Один тест, много случаев
Большинству функций нужна одна и та же проверка для многих входных данных: обычные значения, отрицательные значения, ноль, граничные случаи. Если скопировать тест пять раз, получится пять мест, которые придётся поддерживать.
Обычное решение — табличный тест: поместите случаи в массив объектов — каждый случай содержит имя, входные данные и ожидаемый результат — и пройдите по ним одним циклом.
Добавление нового случая превращается в одну новую строку таблицы. Это самый распространённый шаблон тестов, который вы встретите в реальном коде.
Таблица
Таблица — это массив объектов с именованным типом, который пишется прямо рядом с тестом. Каждый элемент — один случай: имя (для сообщения об ошибке), входные данные и ожидаемый результат.
Цикл вызывает функцию по одному разу для каждого случая и сравнивает результат. Первое несовпадение выбрасывает ошибку и завершает тест, поэтому сообщение содержит имя случая — так вы узнаёте, какая строка сломалась.
Четыре случая в одной таблице
Четыре случая, один цикл, одна проверка. Здесь все проходят, поэтому запуск выводит одну строку ✔ add over the table. Чтобы проверить пятую ситуацию, вы добавите одну строку в таблицу — цикл останется без изменений.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function add(a: number, b: number): number {
return a + b
}
type Case = {
name: string
a: number
b: number
want: number
}
const cases: Case[] = [
{ name: "one plus one", a: 1, b: 1, want: 2 },
{ name: "positive and negative", a: 5, b: -3, want: 2 },
{ name: "both negative", a: -2, b: -4, want: -6 },
{ name: "zeros", a: 0, b: 0, want: 0 }
]
test("add over the table", () => {
for (const c of cases) {
const got = add(c.a, c.b)
assert.equal(got, c.want, `${c.name}: add(${c.a}, ${c.b}) = ${got}; ожидалось ${c.want}`)
}
})
Подтесты
Вызов t.test(name, fn) внутри теста превращает каждый случай таблицы в отдельный подтест. Внешняя тестовая функция получает контекст теста t в качестве аргумента, и каждый случай ожидается через await, чтобы он завершился до начала следующего.
Теперь имена случаев появляются в выводе с отступом под родительским тестом, каждое со своим ✔ или ✖. Благодаря этому сломанный случай легко заметить в длинной таблице.
У каждого случая есть имя
Та же таблица, обёрнутая в t.test. Теперь вывод перечисляет все четыре случая по отдельности под ▶ add, а итог насчитывает пять тестов: родительский плюс его четыре подтеста.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function add(a: number, b: number): number {
return a + b
}
type Case = {
name: string
a: number
b: number
want: number
}
const cases: Case[] = [
{ name: "one plus one", a: 1, b: 1, want: 2 },
{ name: "positive and negative", a: 5, b: -3, want: 2 },
{ name: "both negative", a: -2, b: -4, want: -6 },
{ name: "zeros", a: 0, b: 0, want: 0 }
]
test("add", async (t) => {
for (const c of cases) {
await t.test(c.name, () => {
const got = add(c.a, c.b)
assert.equal(got, c.want, `add(${c.a}, ${c.b}) = ${got}; ожидалось ${c.want}`)
})
}
})
Запуск одного случая отдельно
Тесты можно выбирать из командной строки по имени с помощью --test-name-pattern:
node --test --test-reporter=spec --test-name-pattern 'both negative'
Шаблон сопоставляется с именем каждого зарегистрированного теста. Подтест нельзя выбрать сам по себе — если родительский тест не подходит, ничего внутри него не запускается — поэтому, чтобы это работало, каждый случай получает собственный вызов test(…) верхнего уровня с именем случая внутри.
Это отлично подходит, когда вы чините один сломанный случай: перезапускайте только его, пока он не станет зелёным, а потом запустите всё снова.
Фильтрация по имени
Таблица регистрируется как четыре отдельных теста с именами add: one plus one и так далее. При запуске с шаблоном both negative выполняется только один из четырёх — в итоге указано tests 1.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function add(a: number, b: number): number {
return a + b
}
type Case = {
name: string
a: number
b: number
want: number
}
const cases: Case[] = [
{ name: "one plus one", a: 1, b: 1, want: 2 },
{ name: "positive and negative", a: 5, b: -3, want: 2 },
{ name: "both negative", a: -2, b: -4, want: -6 },
{ name: "zeros", a: 0, b: 0, want: 0 }
]
// Один тест верхнего уровня на каждый случай, чтобы шаблон имени мог выбрать ровно один из них.
for (const c of cases) {
test(`add: ${c.name}`, () => {
const got = add(c.a, c.b)
assert.equal(got, c.want, `add(${c.a}, ${c.b}) = ${got}; ожидалось ${c.want}`)
})
}