Один тест, много случаев
Большинству функций нужна одна и та же проверка для многих входных данных: обычные значения, отрицательные значения, ноль, граничные случаи. Если скопировать тест пять раз, получится пять мест, которые придётся поддерживать.
Подход Go — табличный тест: поместите случаи в срез структур, где каждый случай хранит имя, входные данные и ожидаемый результат, и пройдите по ним одним циклом.
Добавление нового случая превращается в одну новую строку таблицы. Это самый распространённый шаблон тестирования, который вы встретите в реальном коде на Go.
Таблица
Таблица — это срез анонимных структур, объявленный прямо внутри теста. Каждая запись — один тест-кейс: имя (для сообщения о падении теста), входные данные и ожидаемый результат.
Цикл вызывает функцию по одному разу для каждого случая и сообщает о каждом несовпадении через t.Errorf — поэтому видны все сломанные случаи, а не только первый.
Четыре случая в одной таблице
Четыре случая, один цикл, одна проверка. Здесь все проходят. Чтобы проверить пятую ситуацию, вы добавите одну строку в таблицу — цикл останется нетронутым.
Вывод: программа на Go и захваченный вывод
package calc
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"one plus one", 1, 1, 2},
{"positive and negative", 5, -3, 2},
{"both negative", -2, -4, -6},
{"zeros", 0, 0, 0},
}
for _, tc := range cases {
got := Add(tc.a, tc.b)
if got != tc.want {
t.Errorf("%s: Add(%d, %d) = %d; ожидалось %d", tc.name, tc.a, tc.b, got, tc.want)
}
}
}
Подтесты с t.Run
t.Run(name, func(t *testing.T) { … }) превращает каждый случай таблицы в отдельный подтест. Имена случаев теперь появляются в выводе go test -v, например TestAdd/both_negative (пробелы заменяются на подчёркивания).
Каждый подтест проходит или падает сам по себе, поэтому сломанный случай легко заметить в длинной таблице.
Каждый случай получает имя
Та же таблица, обёрнутая в t.Run. В подробном выводе теперь перечислены все четыре случая по отдельности — видно, какие именно случаи выполнились и прошли.
Вывод: программа на Go и захваченный вывод
package calc
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"one plus one", 1, 1, 2},
{"positive and negative", 5, -3, 2},
{"both negative", -2, -4, -6},
{"zeros", 0, 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Add(tc.a, tc.b)
if got != tc.want {
t.Errorf("Add(%d, %d) = %d; ожидалось %d", tc.a, tc.b, got, tc.want)
}
})
}
}
Запуск одного случая отдельно
Подтесты можно выбирать из командной строки с помощью -run, указывая TestName/subtest_name:
go test -v -run "TestAdd/both_negative"
Запустится только подходящий подтест. Это идеально подходит для исправления одного сломанного случая: перезапускайте только его, пока он не станет зелёным, а затем снова запустите всё.
Фильтрация с помощью -run
Тот же файл, что и выше, запущенный с -run "TestAdd/both_negative". Выполняется только один из четырёх случаев — остальные даже не запускаются.
Вывод: программа на Go и захваченный вывод
package calc
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"one plus one", 1, 1, 2},
{"positive and negative", 5, -3, 2},
{"both negative", -2, -4, -6},
{"zeros", 0, 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Add(tc.a, tc.b)
if got != tc.want {
t.Errorf("Add(%d, %d) = %d; ожидалось %d", tc.a, tc.b, got, tc.want)
}
})
}
}