Пакет testing¶
Тестування вбудоване у toolchain. Не треба встановлювати фреймворк,
не потрібна бібліотека для assertions, і не треба налаштовувати
раннер — файл із суфіксом _test.go і функція, що починається з
Test, вже є тестом.
func TestNormalize(t *testing.T) {
got, err := Normalize(" Hello ")
if err != nil {
t.Fatalf("Normalize() unexpected error: %v", err)
}
if got != "hello" {
t.Errorf("Normalize() = %q, want %q", got, "hello")
}
}
Правила¶
- Ім'я файлу закінчується на
_test.go. Такі файли виключені зі звичайних збірок, тож тестовий код ніколи не потрапляє в продакшн. - Функція має вигляд
func TestXxx(t *testing.T). Ім'я післяTestмає починатися з великої літери —Testthingтестом не є, і ніхто вас про це не попередить. - Тестові файли лежать поруч із кодом, який вони тестують, у тому
самому пакеті. Окремого дерева
tests/немає.
Запускайте їх через go test ./... з будь-якого місця в модулі.
Ніяких assertions, лише if¶
Go не постачає assertEqual. Ви порівнюєте через if і повідомляєте
через методи *testing.T:
| Метод | Ефект |
|---|---|
t.Errorf |
зафіксувати провал, продовжити виконання |
t.Fatalf |
зафіксувати провал і зупинити цей тест |
t.Logf |
занотувати щось; показується лише з -v або при провалі |
t.Skipf |
пропустити, з причиною |
Вибір між Errorf і Fatalf — це питання того, чи має сенс
продовжувати. Якщо наступний рядок призведе до паніки — nil-результат,
провалене налаштування — використовуйте Fatalf. Інакше Errorf
повідомляє про кілька проблем за один прогін, замість того щоб
змушувати вас виправляти їх по одній.
t.Fatalf викликає runtime.Goexit, тож зупиняє лише ту горутину
(goroutine), в якій виконується. Виклик цього методу з горутини, яку
запустив ваш тест, не завершує тест належним чином — замість цього
надсилайте провал назад у горутину тесту.
Пишіть повідомлення про провал для того, хто його читатиме¶
Тест, що провалюється з повідомленням false, не каже нічого. Прийнято
виводити, що ви викликали, що отримали і що очікували:
Файл і рядок додаються автоматично. Використовуйте %q для рядків,
щоб відмінності в пробілах були видимі — саме тому "hello " і
"hello" інакше нерозрізнимі у виводі.
Запуск підмножини¶
go test ./... # everything
go test ./internal/store/... # one package tree
go test -v ./... # show each test
go test -run TestNormalize ./... # regex match on the name
go test -count=1 ./... # defeat the result cache
-run приймає регулярний вираз, а не буквальний рядок, тож
-run TestNormalize також збігається з TestNormalizeTable.
Прив'яжіть його якорями через -run '^TestNormalize$', коли це
важливо.
Результати кешуються: незмінений пакет виводить (cached) і не
перезапускається. Зазвичай це саме те, що потрібно, а -count=1 —
спосіб примусити реальний запуск.
Внутрішні та зовнішні тестові пакети¶
Тестовий файл може оголосити один із двох пакетів:
package store // internal: sees unexported identifiers
package store_test // external: only the public API
store_test — єдиний виняток із правила "один пакет на директорію",
і компілятор дозволяє обидва варіанти в одній папці.
Типово використовуйте внутрішню форму. Звертайтесь до store_test,
коли хочете переконатися, що експортоване API придатне до використання
саме собою, або щоб розірвати цикл імпорту — коли тесту потрібен
пакет, що імпортує той, який тестується.
Налаштування і прибирання¶
t.Cleanup реєструє роботу, яка виконається, коли тест завершиться,
включно з провалом. Виконується в порядку "останній зареєстрований —
перший виконаний", як defer, але переживає виклики допоміжних
функцій:
Надавайте йому перевагу над defer у тестах: хелпер може зареєструвати
власне прибирання, чого defer усередині цього хелпера зробити не міг
би.
TestMain дає пакету одноразове налаштування і зобов'язаний
викликати m.Run та завершитися з його кодом:
Зверніть увагу на os.Exit — через нього відкладені функції в
TestMain не виконуються, тож розміщуйте прибирання перед виходом.
Покриття, бенчмарки й детектор гонитв¶
-race інструментує бінарний файл, щоб виявляти одночасний доступ до
однієї й тієї самої пам'яті. Це повільніше, і повідомляє лише про ті
гонитви, що дійсно сталися під час прогону — але знаходить справжні
баги, яких жодне читання коду не знайде. Запускайте його в CI.
Швидкий бенчмарк, розглянутий детально в статті бенчмарки, фазинг і детектор гонитв:
-run XXX не збігається з жодним тестом, тож запускаються лише
бенчмарки.
Функції-приклади¶
Функція з іменем ExampleXxx та коментарем // Output: компілюється,
виконується, і її вивід порівнюється:
Такі функції з'являються в go doc та на pkg.go.dev, тож вони є
документацією, яка не може застаріти. Приберіть коментар // Output:
— і функція компілюватиметься, але не виконуватиметься.
З досвіду Python: найближчий аналог —
pytest, тільки без магії. Немає фікстур, немає переписуванняassert, немає плагінів, немаєconftest.py. Ви пишете звичайні порівняння й звичайні повідомлення про помилки, що вимагає більше набору тексту, але значно легше читати, коли щось ламається о третій ночі.
Швидка довідка¶
| Задача | Форма |
|---|---|
| тест | func TestXxx(t *testing.T) у *_test.go |
| провалити й продовжити | t.Errorf("got %v, want %v", got, want) |
| провалити й зупинити | t.Fatalf(...) |
| прибирання | t.Cleanup(fn) — краще за defer у тестах |
| налаштування на рівні пакета | TestMain(m *testing.M), обов'язково os.Exit(m.Run()) |
| тести лише публічного API | package foo_test |
| запустити все | go test ./... |
| детальний вивід / фільтрація | -v, -run '^TestX$' |
| примусити реальний запуск | -count=1 |
| покриття / гонитви | -cover, -race |
| виконувана документація | ExampleXxx з // Output: |