🧪 Что это
Этот навык (skill) предоставляет набор лучших практик и шаблонов для тестирования кода на Go. Он охватывает все основные аспекты: от модульных тестов (unit tests) до интеграционных тестов (integration tests), включая измерение производительности, поиск гонок данных и анализ покрытия. Навык пригодится как новичкам, которые хотят писать качественные тесты с нуля, так и опытным разработчикам, стремящимся улучшить существующую тестовую базу.
⚙️ Как работает
1. Табличные тесты (table-driven tests)
Основной идиоматичный шаблон в Go. Тестовые случаи описываются в виде среза структур, каждая из которых содержит имя, входные данные и ожидаемый результат. Это позволяет легко добавлять новые кейсы, а вложенные вызовы t.Run() обеспечивают изоляцию и читаемые отчёты.
tests := []struct {
name string
email string
wantErr bool
}{
{"valid", "user@example.com", false},
{"missing @", "userexample.com", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail(%q) error = %v, wantErr %v", tt.email, err, tt.wantErr)
}
})
}
2. Тестовые помощники (test helpers)
Функции-помощники с вызовом t.Helper() упрощают проверки, не загрязняя код повторяющимися условиями. При этом в случае ошибки в логах отображается корректная строка вызова, а не сам helper.
3. Фикстуры и очистка
Для ресурсов (БД, HTTP-серверы) используйте t.Cleanup(): он запускает функцию-очиститель после завершения теста (даже при панике). Это удобнее, чем defer, и не требует явного вызова.
4. 🐞 Детектор гонок (race detector)
Запуск с флагом -race (go test -race ./...) выявляет одновременный доступ к данным без синхронизации. В CI его стоит включать обязательно, так как он отлавливает реальные проблемы, которые проявляются только в production.
5. 📊 Покрытие кода (coverage)
Команды go test -cover и go tool cover -html=coverage.out показывают, какие строки кода выполняются тестами. Можно задать порог (например, 80%) и сделать его обязательным для прохождения CI.
6. ⏱ Бенчмарки (benchmarking)
Функции вида BenchmarkXxx(b *testing.B) измеряют производительность. Используйте b.ResetTimer() для исключения накладных расходов, а benchstat — для сравнения двух замеров (например, до и после оптимизации).
7. Моки (mocking) на основе интерфейсов
Go поощряет использование интерфейсов: для тестирования создаётся структура-мок, реализующая интерфейс, и возвращает заранее заданные значения. Это изолирует тестируемый код от внешних зависимостей.
8. Интеграционные тесты
Помечайте интеграционные тесты build-тегом //go:build integration и запускайте только при необходимости (go test -tags=integration). Для внешних сервисов (например, PostgreSQL) используйте testcontainers-go, чтобы поднимать и удалять контейнеры динамически, с автоматической очисткой через t.Cleanup().
9. Организация файлов и пакетов
- Файлы тестов (например,
user_test.go) располагаются рядом с тестируемым кодом.
- Для black-box тестов используйте отдельный пакет с суффиксом
_test (например, package user_test).
- White-box тесты размещаются в том же пакете, чтобы иметь доступ к внутренним деталям.
- Тестовые данные — в папке
testdata/.
10. Частые сценарии
- HTTP-обработчики: используйте
httptest.NewRequest() и httptest.NewRecorder().
- Контекст с таймаутом: тестируйте логику отмены через
context.WithTimeout.
- Переменные окружения:
t.Setenv() гарантирует восстановление исходного значения.
- Временные файлы:
t.TempDir() создаёт и удаляет директорию автоматически.
🔧 Когда использовать
- При написании нового кода на Go — сразу закладывайте качественные тесты.
- Когда нужно повысить покрытие существующего проекта.
- Для налаживания CI/CD: включите детектор гонок, измерение покрытия и, возможно, интеграционные тесты.
- Если тесты флакают (нестабильны) — табличные тесты и helpers помогают структурировать проверки и быстрее находить причину.
- Для оптимизации производительности — бенчмарки с benchstat.
⚠️ Важно знать
- Детектор гонок (race detector) не ловит все виды гонок, но покрывает большинство реальных случаев.
t.Parallel() ускоряет выполнение, но убедитесь, что тесты действительно независимы (нет общего состояния).
- Не злоупотребляйте флагом
-short для пропуска интеграционных тестов: он должен быть осмысленно встроен в логику (например, if testing.Short() { t.Skip(...) }).
- Избегайте
init() в тестовых файлах — он выполняется для всего пакета и может влиять на другие тесты.
- При использовании
t.Helper() не забывайте вызывать его первой строкой в функции-помощнике.
- Для бенчмарков: не делайте лишних операций внутри цикла
for i := 0; i < b.N; i++, иначе измерение будет некорректным.
Этот навык даёт проверенную базу для создания надёжных и поддерживаемых тестов на Go. Следуя этим практикам, вы уменьшите количество багов в production и упростите рефакторинг.
Комментарии
Комментариев пока нет. Будьте первым.