Модульні уроки (2/5)
Табличні тести
Табличні тести є ідіоматичним шаблоном Go: замість того, щоб писати N окремих TestThisSpecificCase функцій, ви оголошуєте фрагмент випадків і повторюєте. Існує ОДИН блок тверджень, а тестові дані відокремлені від логіки.
Базовий візерунок
func TestAbs(t *testing.T) {
cases := []struct {
name string
in int
want int
}{
{"pos", 3, 3},
{"neg", -3, 3},
{"zero", 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := Abs(tc.in); got != tc.want {
t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
}
})
}
}Три інгредієнти:
- Фрагмент анонімної структури зі звичайними полями:
name, вхідні дані, очікуваний результат (і, можливо, очікувана помилка). for _, tc := range cases— цикл виконання.t.Run(tc.name, func(t *testing.T) { ... })— кожен випадок стає підтестом із власною назвою.
Чому t.Run?
t.Run створює підтест, який має:
- Індивідуальне ім’я відображається у вихідних даних:
TestAbs/pos,TestAbs/neg, ... - Власний
t *testing.T: збій в одному випадку не зупиняє інші (якщо не використовуєтьсяt.Fatal). - Фільтрування командного рядка:
go test -run TestAbs/negзапускає лише цей випадок. - Необов’язковий паралелізм: виклик
t.Parallel()всередині функції для розпаралелювання.
go test -v -run TestAbs/negВипадки з очікуваними помилками
cases := []struct {
name string
in string
want int
wantErr bool
}{
{"ok", "42", 42, false},
{"bad", "x", 0, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := Atoi(tc.in)
if (err != nil) != tc.wantErr {
t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
}
if !tc.wantErr && got != tc.want {
t.Errorf("got %d, want %d", got, tc.want)
}
})
}Підводний камінь із закриттям циклу (до Go 1.22)
У версіях до Go 1.22 змінна tc спільно працювала між ітераціями: виклик t.Parallel() всередині t.Run призводив до того, що всі підтести запускалися проти того самого останнього випадку. Виправлення було:
for _, tc := range cases {
tc := tc // local rebind (pre-1.22)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// ...
})
}Називання відмінків
Кращі практики:
- Коротка, але нагадує назва:
"empty input","too large","negative". - Без пробілів, якщо ви хочете легко фільтрувати за допомогою
-run(або використовувати підкреслення). - Завжди включайте випадок "щасливого шляху", "граничний" випадок (нульовий, порожній) і принаймні один випадок помилки.
вправи
Визначте зріз випадків анонімної структури з полями in і want, заповненими 3 випадками для перевірки Abs (позитивний, негативний, нуль).
Рішення доступне після 3 спроб
Додайте t.Run з tc.name, щоб перетворити кожен випадок на іменований підтест.
Рішення доступне після 3 спроб
У чому головна перевага керованого таблицею шаблону перед N окремими тестовими функціями?
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { ... })
}