Тестирование в Go
| Язык | Go |
|---|---|
| Инструменты | пакет testing, команда go test |
| Файлы тестов | *_test.go |
| Виды | тесты (TestXxx), бенчмарки (BenchmarkXxx), фаззинг (FuzzXxx), примеры (ExampleXxx) |
| Покрытие | go test -cover, -coverprofile |
| Гонки | go test -race |
Тестирование в Go встроено в сам инструментарий языка: пакет testing из стандартной библиотеки и команда go test поддерживают модульные тесты, бенчмарки, фаззинг, проверяемые примеры, покрытие кода и поиск гонок данных. В отличие от многих языков, для этого не нужен сторонний фреймворк — хотя библиотеки-помощники вроде testify популярны.[1][2]
Основы
Тесты лежат рядом с кодом в файлах с суффиксом _test.go и не попадают в итоговый бинарник. Тестовая функция называется TestXxx и принимает *testing.T:
// mathx/abs.go
package mathx
func Abs(x int) int {
if x < 0 {
return -x
}
return x
}
// mathx/abs_test.go
package mathx
import "testing"
func TestAbs(t *testing.T) {
if got := Abs(-3); got != 3 {
t.Errorf("Abs(-3) = %d, want 3", got)
}
}
go test ./... # все пакеты модуля
go test -run TestAbs -v ./mathx
go test -count=1 ./... # без кэша результатов
t.Errorf отмечает ошибку и продолжает выполнение, t.Fatalf — останавливает тест. Встроенных функций-«ассертов» нет намеренно: проверки пишутся обычным if, а сообщение принято строить в форме «got X, want Y».
Табличные тесты и подтесты
Идиоматический стиль Go — табличные тесты: набор случаев описывается срезом структур, а одна петля их проверяет.[3] Каждый случай запускается как подтест через t.Run, что даёт отдельное имя, возможность запустить один случай и параллельность:[5]
func TestAbsTable(t *testing.T) {
tests := []struct {
name string
in int
want int
}{
{"positive", 5, 5},
{"negative", -5, 5},
{"zero", 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := Abs(tt.in); got != tt.want {
t.Errorf("Abs(%d) = %d, want %d", tt.in, got, tt.want)
}
})
}
}
go test -run 'TestAbsTable/negative' запустит только один случай. С Go 1.22 переменная цикла создаётся заново на каждой итерации, поэтому старый приём tt := tt больше не нужен.
Полезные возможности testing.T
t.Helper()— помечает вспомогательную функцию, чтобы ошибка указывала на строку вызывающего теста;t.Cleanup(func(){...})— освобождение ресурсов после теста;t.TempDir()— временный каталог, удаляемый автоматически;t.Setenv("KEY", "v")— временная переменная окружения;t.Skip()и флаг-short— пропуск медленных тестов;TestMain(m *testing.M)— общая подготовка и завершение для пакета.
Бенчмарки
Функции BenchmarkXxx(b *testing.B) измеряют производительность. С Go 1.24 рекомендуемая форма — цикл b.Loop(), который сам управляет числом итераций и не даёт компилятору выбросить измеряемый код:[7]
func BenchmarkAbs(b *testing.B) {
for b.Loop() {
Abs(-42)
}
}
go test -bench=. -benchmem ./mathx
Для сравнения результатов до и после изменения используют утилиту benchstat, которая учитывает статистический разброс; профили для pprof получают флагами -cpuprofile и -memprofile.
Фаззинг
С Go 1.18 фаззинг встроен в go test.[4] Фазз-тест получает случайно мутируемые входные данные и ищет паники или нарушения свойств; найденные «падающие» входы сохраняются в testdata/fuzz и становятся регрессионными тестами:
func FuzzReverse(f *testing.F) {
f.Add("hello")
f.Fuzz(func(t *testing.T, s string) {
if Reverse(Reverse(s)) != s {
t.Errorf("double reverse mismatch for %q", s)
}
})
}
go test -fuzz=FuzzReverse -fuzztime=30s
Примеры
Функции ExampleXxx с комментарием // Output: одновременно служат документацией на pkg.go.dev и тестом: go test сравнивает вывод с ожидаемым.
HTTP и внешние зависимости
Пакет net/http/httptest позволяет тестировать обработчики без сети — через httptest.NewRecorder — или поднять настоящий тестовый сервер на случайном порту через httptest.NewServer.[6] Зависимости от баз и внешних API в Go обычно заменяют через небольшие интерфейсы и собственные фейковые реализации; для интеграционных тестов с настоящей PostgreSQL популярен testcontainers-go, поднимающий контейнеры Docker.
Покрытие и гонки
go test -cover ./...
go test -coverprofile=cover.out ./... && go tool cover -html=cover.out
go test -race ./...
Флаг -race включает детектор гонок данных, который находит одновременный доступ к памяти из разных горутин без синхронизации (см. Состояние гонки и блокировки). В CI/CD тесты обычно запускают с -race вместе с go vet — последний и так частично выполняется при go test.
Библиотеки
Стандартных средств хватает большинству проектов, но популярны: testify (assert, require, моки), go-cmp (cmp.Diff для наглядного сравнения структур), gomock и mockery (генерация моков), Ginkgo/Gomega (BDD-стиль). В Python похожую роль играет pytest.