Табличные тесты через пакет testing и go test
Содержание курса
В прошлом уроке мы вынесли функцию Square в пакет mathutil. Теперь у неё есть exported-имя и отдельная папка — всё готово, чтобы написать к ней полноценные тесты. Именно сюда и смотрит go test.
Файл _test.go, сигнатура TestXxx и выбор между t.Errorf и t.Fatalf
Создайте в папке mathutil файл mathutil_test.go. Суффикс _test.go — это не соглашение команды, а правило инструментальной цепочки Go: такие файлы компилируются только при вызове go test и не попадают в обычный бинарник пакета. Внутри объявляйте тот же пакет:
package mathutil
import "testing"
func TestSquare(t *testing.T) {
got := Square(3)
if got != 9 {
t.Errorf("Square(3) = %d, want 9", got)
}
}
Почему go test запустит TestSquare, но не запустит testSquare или squareTest? Правило discovery состоит из двух частей.
Часть 1 — имя функции. Имя должно начинаться ровно с Test, а символ сразу после Test не должен быть строчной буквой. Это означает, что подходят: TestSquare (заглавная S), Test_square (знак подчёркивания), Test (конец имени). Не подходят: Testsquare (строчная s), testSquare (сам Test написан со строчной t). Функции с нераспознанным именем go test игнорирует — они остаются обычным вспомогательным кодом.
Часть 2 — сигнатура. Если имя распознано как тест, функция обязана принимать ровно один параметр *testing.T и ничего не возвращать. Нарушение сигнатуры — это уже не тихое игнорирование: go test выдаёт ошибку сборки вида wrong signature for TestXxx. То есть неверная сигнатура при правильном имени ломает прогон, а не просто пропускается.
Теперь о выборе между двумя методами фиксации ошибок.
t.Errorf помечает тест как провальный и записывает сообщение, но выполнение функции продолжается дальше. Это полезно, когда один тест проверяет несколько независимых условий и вы хотите увидеть все нарушения сразу:
func TestSquare(t *testing.T) {
if Square(0) != 0 {
t.Errorf("Square(0) = %d, want 0", Square(0))
}
if Square(-2) != 4 {
t.Errorf("Square(-2) = %d, want 4", Square(-2))
}
}
Оба нарушения попадут в отчёт за один прогон.
t.Fatalf делает то же самое, но после этого немедленно останавливает текущую тест-функцию — технически через runtime.Goexit. Используйте его, когда продолжать бессмысленно: например, если первая проверка вернула nil вместо объекта, а следующий код будет разыменовывать этот указатель и вызовет панику.
result, err := ParseConfig(input)
if err != nil {
t.Fatalf("ParseConfig вернул ошибку: %v", err)
}
// без t.Fatalf здесь был бы nil-deref
if result.Name != "expected" {
t.Errorf("Name = %q, want \"expected\"", result.Name)
}
Практическое правило простое: если следующие строки теста зависят от того, что предыдущая проверка прошла — t.Fatalf. Если проверки независимы — t.Errorf.
