Урок по треку Go 7 мин

Тесты, бенчмарки и модули

Табличные тесты, t.Run, моки через интерфейсы, бенчмарки и fuzzing, go.mod, версии и MVS.

В Go тестирование встроено в инструменты: никаких фреймворков ставить не нужно. И зависимости тоже управляются самим go.

Тесты

Файл xxx_test.go, функция TestXxx(t *testing.T), запуск go test ./....

func TestAbs(t *testing.T) {
    tests := []struct {
        name string
        in   int
        want int
    }{
        {"положительное", 3, 3},
        {"отрицательное", -3, 3},
        {"ноль", 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.
  • t.Errorf отмечает провал и продолжает, t.Fatalf останавливает текущий тест.
  • t.Run создаёт подтест, запуск одного: go test -run 'TestAbs/ноль'.
  • t.Parallel() запускает подтесты параллельно. С Go 1.22 захват tt в цикле больше не баг.
  • t.Helper() в хелперах, чтобы в ошибке была строка вызова. t.Cleanup для уборки, t.TempDir() для временной папки, t.Setenv для переменных окружения.
  • TestMain(m *testing.M) для общей подготовки пакета.
  • Кеш: повторный go test берёт результат из кеша, если ничего не изменилось. Сбросить: -count=1.

Моки

Mock в Go обычно делают через интерфейс, объявленный у потребителя:

type UserStore interface { Get(ctx context.Context, id int) (User, error) }

type fakeStore struct{ u User }
func (f fakeStore) Get(context.Context, int) (User, error) { return f.u, nil }

Генераторы моков: go.uber.org/mock (mockgen), mockery. Для HTTP есть httptest.NewRecorder и httptest.NewServer. Для интеграции с базой часто берут testcontainers.

Покрытие и race

go test -cover, -coverprofile=c.out и go tool cover -html=c.out. go test -race обязательно в CI для конкурентного кода.

Бенчмарки

func BenchmarkJoin(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = strings.Join(parts, ",")
    }
}

Запуск go test -bench=. -benchmem: время на операцию, байты и число аллокаций на операцию. b.N подбирает фреймворк. С Go 1.24 удобнее for b.Loop() { ... }: подготовка до цикла не попадает в замер, а результат не выкидывается оптимизатором. Сравнивать результаты до и после правки: benchstat.

Fuzzing

С Go 1.18: func FuzzParse(f *testing.F), f.Add(seed), f.Fuzz(func(t *testing.T, s string) {...}). Запуск go test -fuzz=FuzzParse. Найденные падения сохраняются в testdata/fuzz и потом работают как обычные тесты.

Примеры

func ExampleAbs() с комментарием // Output: 3 это и документация, и тест.

Модули

module github.com/acme/shop

go 1.24

require (
    github.com/jackc/pgx/v5 v5.7.1
    golang.org/x/sync v0.10.0
)
  • go mod init, go get pkg@v1.2.3, go mod tidy (добавить нужное и убрать лишнее).
  • go.sum хранит хеши содержимого модулей, его коммитят. Проверка идёт ещё и через публичную базу sum.golang.org.
  • Семантическое версионирование. Мажорная версия 2 и выше входит в путь импорта: .../v2. Это разные модули.
  • MVS (minimal version selection): из всех требований к модулю выбирается максимальная из минимально требуемых версий, а не самая свежая. Сборка воспроизводима без lock-файла.
  • Директива go задаёт версию языка (и, например, семантику переменной цикла), toolchain подсказывает версию инструментов. С Go 1.21 go скачивает нужный тулчейн сам.
  • replace для локальной разработки, go.work для нескольких модулей в одном репо.
  • Приватные модули: GOPRIVATE=github.com/acme/*, прокси по умолчанию proxy.golang.org.
  • Директория internal/ закрывает пакеты от импорта извне родителя.
  • С Go 1.24 в go.mod есть директива tool для утилит разработки.

Что спросят на собесе

  • Как писать табличные тесты и подтесты.
  • Как мокать зависимости в Go.
  • Как написать бенчмарк и что показывает -benchmem.
  • Что такое MVS и зачем go.sum.
  • Как устроены мажорные версии модулей и что делает go mod tidy.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.