testify¶
Твердження (assertions) з читабельними повідомленнями про провал. Пакет
testing в Go дає вам if
і t.Errorf; testify дає один рядок на перевірку і diff, коли вона
провалюється.
Модуль:
github.com/stretchr/testify.
assert не зупиняє нічого, require зупиняє все¶
Два пакети, однакові назви функцій, одна відмінність:
| Пакет | При провалі |
|---|---|
assert |
записує й продовжує — як t.Errorf |
require |
записує й зупиняє тест — як t.Fatalf |
Звідси й правило: require для всього, від чого залежить решта тесту,
assert — для незалежних перевірок.
u, err := Load("1")
require.NoError(t, err) // stop here if it failed
require.Equal(t, "Ada", u.Name)
assert.Len(t, u.Tags, 2) // independent checks
assert.Contains(t, u.Tags, "a")
Використання assert.NoError там, де належить require.NoError —
поширена помилка: тест продовжується до розіменування nil, а паніка
ховає справжнє повідомлення.
Порядок аргументів¶
Спочатку t, потім очікуване, потім фактичне. Переплутайте
порядок — і тест усе одно пройде чи провалиться правильно, але diff
перевернутий і читається як брехня.
Як виглядає провал¶
Error Trace: lib_test.go:27
Error: Not equal:
expected: "Bob"
actual : "Ada"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-Bob
+Ada
Test: TestFailureOutput
Messages: name should match the seeded value
Ось причина її використовувати. На структурі чи зрізі diff показує поле, що відрізняється, замість того, щоб виводити обидва значення й залишати порівняння вам.
Кожне твердження бере опціональне завершальне повідомлення:
Варто додавати, коли саме твердження не каже, чому це важливо.
Твердження, якими ви користуватиметесь насправді¶
require.NoError(t, err)
require.Error(t, err)
assert.ErrorIs(t, err, ErrEmpty) // unwraps, like errors.Is
assert.ErrorAs(t, err, &target)
assert.Equal(t, want, got)
assert.NotEqual(t, a, b)
assert.Nil(t, v)
assert.True(t, ok)
assert.Len(t, u.Tags, 2)
assert.Empty(t, list)
assert.Contains(t, u.Tags, "a")
assert.ElementsMatch(t, []string{"b", "a"}, u.Tags) // ignores order
assert.InDelta(t, 3.14159, 3.1416, 0.001) // floats
assert.JSONEq(t, `{"a":1,"b":2}`, `{"b":2,"a":1}`) // ignores key order
ErrorIs — те, до чого тягнутися для помилок — він розгортає, тож
працює й через fmt.Errorf("...: %w", err).
ElementsMatch і JSONEq прибирають цілих дві категорії нестабільних
тестів: порядок у зрізі й порядок ключів у JSON — жодне з яких ви
зазвичай не мали намір перевіряти.
InDelta існує тому, що assert.Equal на числах з плаваючою комою
порівнює точно, а точне порівняння чисел з плаваючою комою — це баг.
Equal розрізняє nil і порожнє¶
assert.Equal використовує семантику reflect.DeepEqual, тому:
nil-зріз і порожній зріз поводяться однаково в кожній операції Go —
len, range, append — і відрізняються тут. Коли ви маєте на увазі
"жодних елементів", використовуйте assert.Empty, який приймає обидва
варіанти.
Те саме стосується мап. Це постійно ловить людей при порівнянні декодованої структури JSON з літералом.
Не викликайте require з горутини¶
require провалюється через t.FailNow, що викликає runtime.Goexit.
З горутини (goroutine), яку запустив ваш тест, це вбиває лише цю
горутину — тест не провалюється правильно й може зависнути в очікуванні
на неї.
Надішліть результат назад у горутину тесту й перевіряйте там, або
використовуйте assert (яка лише записує) всередині горутини.
Решта бібліотеки¶
testify/mock генерує моки на основі очікувань, а testify/suite
додає класи налаштування й завершення в стилі xUnit. Багато кодових баз
на Go використовують лише assert і require і нічого більше, з
причин зі статті про фейки та заглушки:
написані вручну дублікати зрозуміліші за EXPECT().Times(1), а
t.Cleanup покриває те, що робило б завершення в suite.
Спершу засвойте два пакети тверджень. Додавайте решту, лише якщо натрапите на щось, що вони справді розв'язують.
Чи варта вона залежності¶
Чесний аргумент проти: стандартна бібліотека може все це, а бібліотеки тверджень мають звичку розростатись, доки тести не пишуться DSL-ем замість Go.
Аргумент за: вивід diff, і те, що require.NoError(t, err) — один
рядок там, де звичайна версія — три. На великому наборі тестів це
реальна відмінність у тому, скільки сигналу несе кожен тест.
Це також майже загальноприйнято в кодових базах на Go, тож ідіому знає більшість читачів.
З досвіду Python: це переписування тверджень
pytest, зроблене явним — ви викликаєтеassert.Equalзамістьassert x == yй отримуєте порівнянний diff.requireпротиassert— відмінність, якої вpytestнемає, оскільки там кожне провалене твердження зупиняє тест.
Швидка довідка¶
| Потреба | Виклик |
|---|---|
| провалитись і зупинитись | require.X(t, ...) |
| провалитись і продовжити | assert.X(t, ...) |
| порядок | (t, expected, actual) |
| без помилки | require.NoError(t, err) |
| конкретна помилка | assert.ErrorIs(t, err, ErrX) — розгортає |
| довжина / належність | assert.Len, assert.Contains |
| зріз без урахування порядку | assert.ElementsMatch |
| JSON | assert.JSONEq |
| числа з плаваючою комою | assert.InDelta |
| nil або порожнє | assert.Empty — не Equal |
| усередині горутини | ніколи require; надсилайте результат назад |