Тестирование в Go

Материал из llmdocs — справочник разработчика
Портал: Go и Rust
Тестирование в 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.

См. также

Источники

  1. Go — package testing
  2. Go — Command go: Test packages
  3. Go Wiki — TableDrivenTests
  4. Go — Tutorial: Getting started with fuzzing
  5. Go Blog — Using Subtests and Sub-benchmarks
  6. Go — package net/http/httptest
  7. Go Blog — More predictable benchmarking with testing.B.Loop