Every language
12 languages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
import { describe, expect, it } from 'vitest';
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
describe('clamp', () => {
it.each([
[5, 0, 10, 5], // in range
[-3, 0, 10, 0], // below
[99, 0, 10, 10], // above
])('clamp(%i, %i, %i) → %i', (n, lo, hi, want) => {
expect(clamp(n, lo, hi)).toBe(want);
});
});it.each printf-formats the title per row (%s strings, %i ints, %j JSON) — that title IS the failure name: the report reads 'clamp(99, 0, 10) → 5', not a line number every row shares. describe.each is the same mechanism one level up (a suite per row); a plain for-loop around expect() has no title to format.
TSTypeScript
import { expect, it } from 'vitest';
const clamp = (n: number, lo: number, hi: number): number =>
Math.min(hi, Math.max(lo, n));
const cases = [
[5, 0, 10, 5], // in range
[-3, 0, 10, 0], // below
[99, 0, 10, 10], // above
] as const; // rows stay 4-tuples — without this the table is number[][]
it.each(cases)('clamp(%i, %i, %i) → %i', (n, lo, hi, want) => {
expect(clamp(n, lo, hi)).toBe(want);
});as const keeps each row a 4-tuple — without it the table widens to number[][] and a row with the wrong arity fails at runtime, not compile time (append `satisfies ReadonlyArray<readonly [number, number, number, number]>` to pin the arity in the type). The %i title is still what vitest prints when the row fails.
GoGo
import "testing"
func clamp(n, lo, hi int) int { return min(hi, max(lo, n)) } // min/max: Go 1.21+ builtins
func TestClamp(t *testing.T) {
tests := []struct {
name string
n, lo, hi int
want int
}{
{"in range", 5, 0, 10, 5},
{"below", -3, 0, 10, 0},
{"above", 99, 0, 10, 10},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { // the subtest name IS the row's name
if got := clamp(tt.n, tt.lo, tt.hi); got != tt.want {
t.Errorf("clamp(%d, %d, %d) = %d, want %d", tt.n, tt.lo, tt.hi, got, tt.want)
}
})
}
}t.Run(tt.name, ...) turns each row into a subtest: a failure prints '--- FAIL: TestClamp/below' and 'go test -run TestClamp/below' re-runs exactly that row. Asserting bare inside the loop reports one anonymous file:line for every case — the name field must travel into t.Run, never just into the error message.
RsRust
#[test]
fn clamp_cases() {
let cases: Vec<(i32, i32, i32, i32)> = vec![
(5, 0, 10, 5), // in range
(-3, 0, 10, 0), // below
(99, 0, 10, 10), // above
];
for (n, lo, hi, want) in cases {
assert_eq!(n.clamp(lo, hi), want, "clamp({n}, {lo}, {hi})");
}
}std has no parametrize — the loop over a Vec of tuples is the whole idiom, and assert_eq!'s trailing message slot ('clamp({n}, {lo}, {hi})') is the only thing that identifies the failing row; the message is formatted on failure only. #[should_panic] is the tool for must-error cases; the rstest crate's #[rstest]/#[case] adds per-row subtests without the loop. Note n.clamp(lo, hi) panics when lo > hi.
PHPPHP
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
function clamp(int $n, int $lo, int $hi): int
{
return max($lo, min($hi, $n));
}
final class ClampTest extends TestCase
{
public static function cases(): \Generator // public static — non-negotiable
{
yield 'in range' => [5, 0, 10, 5];
yield 'below' => [-3, 0, 10, 0];
yield 'above' => [99, 0, 10, 10];
}
#[DataProvider('cases')]
public function testClamp(int $n, int $lo, int $hi, int $want): void
{
self::assertSame($want, clamp($n, $lo, $hi));
}
}DataProvider methods MUST be public static — a visibility slip fails every row with a 'data provider not found' error, not a test failure. The yield key ('below') becomes the test's name in the output; numeric keys degrade to 'testClamp with data set #0'. A generator streams big tables that a returned array would materialize.
PyPython
import pytest
def clamp(n: int, lo: int, hi: int) -> int:
return min(hi, max(lo, n))
@pytest.mark.parametrize('n, lo, hi, want', [
(5, 0, 10, 5), # in range
(-3, 0, 10, 0), # below
(99, 0, 10, 10), # above
])
def test_clamp(n, lo, hi, want):
assert clamp(n, lo, hi) == wantpytest auto-derives each row's id from its values — test_clamp[99-0-10-10] — and that id is both the failure name and the -k selector ('pytest -k 99' runs one row). Pass ids=['in range', ...] (or ids=some_function) when the values don't read as names; the ids must stay in row order.
C#C#
using System;
using Xunit;
public class ClampTests
{
[Theory] // Fact runs once; Theory runs once per row
[InlineData(5, 0, 10, 5)] // in range
[InlineData(-3, 0, 10, 0)] // below
[InlineData(99, 0, 10, 10)] // above
public void Clamps(int n, int lo, int hi, int want)
=> Assert.Equal(want, Math.Clamp(n, lo, hi));
}[Theory] vs [Fact] is the whole split: a Fact runs once, a Theory runs once per data row — and xUnit names each row by formatting the arguments into the method name (Clamps(n: 99, lo: 0, hi: 10, want: 10)), which is the failure name and the IDE filter. InlineData takes compile-time constants only; [MemberData]/[ClassData] for computed tables.
JvJava
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ClampTest {
static int clamp(int n, int lo, int hi) {
return Math.max(lo, Math.min(hi, n));
}
@ParameterizedTest(name = "clamp({0}, {1}, {2}) → {3}")
@CsvSource({
"5, 0, 10, 5", // in range
"-3, 0, 10, 0", // below
"99, 0, 10, 10" // above
})
void clamps(int n, int lo, int hi, int want) {
assertEquals(want, clamp(n, lo, hi));
}
}The name attribute carries the row: JUnit's default display name is 'clamps(int,int,int,int) [1]' — an argument-less index that tells you nothing about the case. {0}..{3} splice the actual arguments into the title. @CsvSource rows are strings (fine for scalars); @MethodSource replaces it when rows need real objects.
SwSwift
import XCTest
import Testing
func clamp(_ n: Int, _ lo: Int, _ hi: Int) -> Int { min(hi, max(lo, n)) }
final class ClampTests: XCTestCase {
func testClamp() {
let cases: [(name: String, n: Int, lo: Int, hi: Int, want: Int)] = [
("in range", 5, 0, 10, 5),
("below", -3, 0, 10, 0),
("above", 99, 0, 10, 10),
]
for c in cases {
XCTAssertEqual(clamp(c.n, c.lo, c.hi), c.want,
"case '\(c.name)': clamp(\(c.n), \(c.lo), \(c.hi))")
}
}
}
// Swift Testing — the modern form: one test per row, named by its arguments
@Test(arguments: [
(5, 0, 10, 5), (-3, 0, 10, 0), (99, 0, 10, 10),
])
func clamps(_ n: Int, _ lo: Int, _ hi: Int, want: Int) {
#expect(clamp(n, lo, hi) == want)
}XCTest has no parameterization: one test method looping over labeled tuples, and XCTAssertEqual's message: argument is the only failure naming — thread the case's own label into it or every failure reads the same. Swift Testing (the successor, default in new templates) fixes this: @Test(arguments:) runs a separate test per row and names each by its argument values.
KtKotlin
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
data class ClampCase(val n: Int, val lo: Int, val hi: Int, val want: Int)
class ClampTest {
companion object {
@JvmStatic // required — @MethodSource needs a static factory
fun cases() = listOf(
ClampCase(5, 0, 10, 5), // in range
ClampCase(-3, 0, 10, 0), // below
ClampCase(99, 0, 10, 10), // above
)
}
@ParameterizedTest(name = "{0}") // the data-class toString names the row
@MethodSource("cases")
fun clamps(c: ClampCase) = assertEquals(c.want, c.n.coerceIn(c.lo, c.hi))
}One data class per row beats @CsvSource's stringly columns — fields are typed, and the data class's generated toString IS the display name (ClampCase(n=99, lo=0, hi=10, want=10)), so name = '{0}' needs no manual format string. Same JUnit 5 annotations as Java; @JvmStatic on the companion factory is the Kotlin-only requirement.
RbRuby
def clamp(n, lo, hi)
[lo, [n, hi].min].max
end
RSpec.describe '#clamp' do
[
[5, 0, 10, 5], # in range
[-3, 0, 10, 0], # below
[99, 0, 10, 10], # above
].each do |n, lo, hi, want|
describe "clamp(#{n}, #{lo}, #{hi})" do # fresh group per row
subject { clamp(n, lo, hi) }
it { is_expected.to eq want }
end
end
endThe nested describe per row is what makes subject safe: subject defined once on a shared outer group is overwritten by every loop iteration, so every example would see the LAST row's subject. The interpolated describe string ('clamp(99, 0, 10)') names the failure and rspec --example selects one row. minitest has no row-name machinery at all — loop inside one test method and pass assert_equal's message arg (assert_equal want, got, msg) naming the inputs.
ZigZig
const std = @import("std");
fn clamp(n: i32, lo: i32, hi: i32) i32 {
return @min(hi, @max(lo, n));
}
test "clamp" {
const Case = struct { name: []const u8, n: i32, lo: i32, hi: i32, want: i32 };
const cases = [_]Case{
.{ .name = "in range", .n = 5, .lo = 0, .hi = 10, .want = 5 },
.{ .name = "below", .n = -3, .lo = 0, .hi = 10, .want = 0 },
.{ .name = "above", .n = 99, .lo = 0, .hi = 10, .want = 10 },
};
for (cases) |tc| {
std.debug.print("clamp case: {s}\n", .{tc.name}); // names the row pre-assert
try std.testing.expectEqual(tc.want, clamp(tc.n, tc.lo, tc.hi));
}
}The case array is comptime-known — a new row edits the table, never the loop. Zig prints no per-row name when expectEqual fails, so std.debug.print the case first: the last name printed before the error is the failing row (and the printed inputs double as the repro). One test block per case, with a comptime-generated name via inline for, is the stronger alternative.