Тест фиксирует контракт: таблица случаев, при необходимости t.Parallel и httptest без сети. Бенчмарк с ReportAllocs измеряет аллокации. Покрытие без граничных случаев само по себе ничего не доказывает.
Рантайм и инструменты Go golang собеседование
Тест в Go — функция TestXxx(t *testing.T) в файле _test.go. Это не скриншот моков и не «покрытие 80%» как цель. Хороший тест называет поведение: парсер отвергает пустую строку, хендлер отдаёт 404, репозиторий мапит ErrNoRows.
На бэкенде слои разные. Домен и чистые функции тестируют без БД. HTTP — через httptest, не через реальный порт в CI, если достаточно хендлера. Интеграция с Postgres — testcontainers или общая тестовая база. Мок уместен на границе, которую вы не хотите поднимать.
Аналогия: чеклист перед вылетом. Каждый пункт — конкретное поведение, не «самолёт в целом зелёный на 80%». Нагрузочный прогон двигателя — бенчмарк. Он не заменяет чеклист закрылок.
Table-driven и HTTP
Таблица случаев плюс t.Run даёт имя каждому сценарию. Имя видно в -v и в фейле. Параллель t.Parallel() ускоряет пакет, но делит состояние: глобальные map, синглтоны, слушающий порт — гонки в тестах. Изоляция важнее микросекунд CI.
func TestParse(t *testing.T) {
cases := []struct {
in string
want int
wantErr bool
}{
{in: "1", want: 1},
{in: "x", wantErr: true},
}
for _, tc := range cases {
t.Run(tc.in, func(t *testing.T) {
got, err := strconv.Atoi(tc.in)
if tc.wantErr {
if err == nil {
t.Fatal("expected error")
}
return
}
if err != nil || got != tc.want {
t.Fatalf("got %d err %v want %d", got, err, tc.want)
}
})
}
}
Таблица случаев и подтесты. Имя t.Run видно в -v и в фейле.
Пример. Хендлер должен отдать 404, если пользователя нет. Тест бьёт в httptest.NewRecorder, без listen на порту. Имя подтеста — not_found, а не TestHandler_2.
httptest.NewRecorder ловит ответ хендлера. httptest.NewServer поднимает локальный сервер на loopback: клиент ходит как в сеть, но без интернета. Так проверяют middleware, редиректы, тело. Это не прод-балансировщик и не замена контрактным тестам API на стенде.
Бенчмарки и границы мока
Функция BenchmarkXxx(b *testing.B) крутит цикл b.N раз. Компилятор не должен выкинуть работу: результат используют. b.ReportAllocs() показывает allocs/op — то же давление на кучу, о котором в лекции про побег на кучу. Сравнивают до и после изменения, на той же машине, с пониманием шума.
func BenchmarkSum(b *testing.B) {
data := make([]int, 1024)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = sum(data)
}
}
Бенчмарк с учётом аллокаций. b.N задаёт рантайм тестов.
sqlmock проверяет, что вы отправили ожидаемый SQL — хрупко к тексту запроса. Интерфейс репозитория в домене позволяет подменить хранилище фейком. Если инвариант живёт в уникальном индексе Postgres, фейк его не поймает: нужен интеграционный тест. Выбор — какой контракт вы фиксируете, не «моки всегда плохо».
Пример. Уникальный email в БД. Юнит-тест с фейком репозитория «всегда ок» зелёный. Интеграционный тест ловит второй INSERT тем же email. Мок драйвера этого индекса не знает.
Типичная ошибка
Гнать покрытие процентом без пустого входа, ошибки и таймаута. Шарить глобальное состояние при t.Parallel. Бенчмаркать с «дырой», где компилятор выкинул вызов. Поднять NewServer и считать, что это нагрузочный тест прода.
На собеседовании
Спросите, какой контракт фиксирует тест: вход, ошибка, статус HTTP. Table-driven даёт имена случаев. httptest изолирует хендлер. ReportAllocs — про кучу в горячем пути. Покрытие без границ — слабый сигнал.