Небольшие правила, большая разница
Хорошие тесты следуют нескольким привычкам:
1. Понятные имена — greet falls back for an empty name говорит, что сломалось, без открытия файла.
2. Одно поведение на тест — маленькие тесты указывают прямо на проблему.
3. Таблицы для множества случаев — один цикл вместо скопированных тестов.
4. Сообщения с got и want — сбой должен объяснять себя сам.
5. Отдельные тесты, чтобы увидеть всё; один тест там, где продолжать бессмысленно.
6. Запускайте тесты часто — после каждого небольшого изменения, а не один раз в конце.
7. Тесты живут рядом с кодом — та же папка, суффикс .test.ts.
Сообщения о сбоях, которые помогают
Упавший тест читают в момент растерянности — сообщение должно нести всю историю: какой вызов, что вернулось, что ожидалось.
Хорошая форма — func(input) = got; want want. Оборачивание строк в JSON.stringify добавляет вокруг них кавычки, поэтому пропущенный пробел или пропущенная запятая сразу видны.
Сообщение со всей историей
greet возвращает неправильное приветствие. Наше сообщение показывает вызов, полученное и ожидаемое значение, а исполнитель тестов добавляет ниже свои строки + actual - expected — ошибка (неправильное слово, пропущенные запятая и восклицательный знак) видна по одному только выводу, без открытия кода.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
// В greet есть ошибка: неправильное слово, пропущены запятая и "!".
function greet(name: string): string {
return "Здравствуй " + name
}
test("greet by name", () => {
const got = greet("Ana")
const want = "Привет, Ana!"
assert.equal(got, want,
`greet("Ana") = ${JSON.stringify(got)}; want ${JSON.stringify(want)}`)
})
Одно поведение на тест
У теста, который проверяет пять вещей, есть пять причин упасть — а его имя может описать только одну из них. Разделение по поведению делает каждый тест маленьким, а его имя — честным. Здесь это важно вдвойне, потому что упавшая проверка останавливает свой тест: пять проверок в одном тесте означают, что вы всегда увидите только первый сбой.
Обычное приветствие и запасной вариант для пустого имени — это два поведения, поэтому им достаются два теста. Когда один падает, его имя уже говорит вам, какое поведение сломалось.
Два поведения — два теста
greet uses the name проверяет обычный случай, greet falls back for an empty name — запасной вариант. Вывод перечисляет их отдельно — сбой указал бы ровно на одно поведение.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function greet(name: string): string {
if (name === "") {
return "Привет, незнакомец!"
}
return "Привет, " + name + "!"
}
test("greet uses the name", () => {
const got = greet("Ana")
assert.equal(got, "Привет, Ana!", `greet("Ana") = ${JSON.stringify(got)}`)
})
test("greet falls back for an empty name", () => {
const got = greet("")
assert.equal(got, "Привет, незнакомец!", `greet("") = ${JSON.stringify(got)}`)
})
Сколько проверено: покрытие
node --test --experimental-test-coverage измеряет, какую часть вашего кода тесты действительно выполняют. Он печатает по одной строке на файл с тремя процентами — строки, ветви и функции — плюс номера строк, до которых ничего не дошло.
Это число — компас, а не цель: низкое значение показывает непроверенные пути. Покрытие 100% не доказывает, что код правильный, но непокрытые строки гарантированно не проверены.
Измеряем покрытие
У describeTemperature три ветви, а тест задействует одну. Отчёт сообщает line % 76.47 и branch % 60.00 и называет непокрытые строки 6-7 9-10 — возврат мороз и возврат жара. Тесты для холодной и горячей ветви подняли бы оба числа.
Измеряется весь файл, включая тесты, и поэтому число для строк не ниже: сами строки тестов все выполнились.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function describeTemperature(celsius: number): string {
if (celsius < 0) {
return "мороз"
}
if (celsius > 30) {
return "жара"
}
return "умеренно"
}
test("20 degrees is mild", () => {
const got = describeTemperature(20)
assert.equal(got, "умеренно", `describeTemperature(20) = ${JSON.stringify(got)}`)
})