> Как организовано тестирование в команде (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Orion_soft

Стек: Go

> Пример ответа

Короткий ответ

Тестирование в команде строится по пирамиде: unit-тесты на уровне функций и пакетов, интеграционные тесты с реальными зависимостями (БД, Redis, внешние API), e2e-тесты на критический пользовательский путь. Обязательны code review с проверкой покрытия, CI-пайплайн с запуском тестов на каждый PR, отдельные тестовые окружения. Для Go используем стандартный testing, табличные тесты, моки через интерфейсы, testcontainers для интеграционных проверок. Метрики покрытия - не самоцель, но порог в 70-80% на ключевых пакетах поддерживаем.

Подробное объяснение

Тестирование - это не просто набор тестов, а процесс, встроенный в разработку. В команде выделяем три уровня:

  • Unit-тесты - изолированно тестируют функции и методы. В Go это табличные тесты с кейсами: нормальное поведение, граничные значения, ошибки. Моки создаём через интерфейсы, чтобы не поднимать реальные зависимости.
  • Интеграционные тесты - проверяют взаимодействие с реальными компонентами: PostgreSQL, Redis, Kafka, внешние сервисы. Используем testcontainers для поднятия зависимостей в Docker. Это медленнее unit-тестов, поэтому запускаем отдельно или с тегом //go:build integration.
  • E2E-тесты - минимальный набор, покрывающий ключевые сценарии: регистрация, оплата, выгрузка данных. Запускаются на отдельном окружении после деплоя.

Процесс в команде:

  1. Каждый PR обязан содержать тесты на новую логику. Без этого ревью не проходит.
  2. CI-пайплайн: lint → unit-тесты → интеграционные тесты → сборка артефакта. На каждый push в ветку.
  3. Code review: проверяем не только код, но и качество тестов - есть ли проверка ошибок, граничных случаев, не тестируем ли мы реализацию вместо поведения.
  4. Тестовые окружения: staging с копией продакшн-данных (анонимизированных), dev-окружение для разработчиков.
  5. Покрытие: смотрим на go test -cover, но не как на KPI, а как на индикатор проблемных зон. Если покрытие падает ниже 70% на критическом пакете - это повод для обсуждения.

Для Go специфично:

  • Интерфейсы - основной инструмент для мокирования. Не мокаем конкретные типы, только интерфейсы.
  • httptest для тестирования HTTP-хендлеров.
  • testcontainers для интеграционных тестов с БД.
  • Генерация моков через mockgen или moq, но предпочитаем ручные моки для простых случаев.

На практике

В реальной работе тестирование - это часть каждого этапа:

  • На этапе проектирования: обсуждаем, какие тесты нужны, какие зависимости мокать, какие интеграции проверять.
  • При написании кода: пишем тесты параллельно с реализацией, а не после. TDD не навязываем, но для сложной логики он работает хорошо.
  • При ревью: проверяем, что тесты реально проверяют поведение, а не просто покрывают строки. Например, тест на функцию с ошибкой должен проверять, что ошибка возвращается, а не просто что функция не паникует.
  • При деплое: интеграционные тесты на staging перед выкаткой в прод.

Пример из практики: у нас был сервис платежей. Unit-тесты покрывали расчёт комиссий и валидацию запросов. Интеграционные тесты поднимали PostgreSQL и проверяли транзакции. E2E-тест проходил полный путь: создание заказа → оплата → обновление статуса. Это позволило ловить регрессии до попадания в прод.

Пример кода

Табличный тест для функции расчёта комиссии:

GO
func TestCalculateCommission(t *testing.T) {
tests := []struct {
name string
amount float64
rate float64
expected float64
wantErr bool
}{
{"normal case", 1000, 0.02, 20, false},
{"zero amount", 0, 0.02, 0, false},
{"negative rate", 1000, -0.02, 0, true},
{"large amount", 1_000_000, 0.02, 20_000, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CalculateCommission(tt.amount, tt.rate)
if (err != nil) != tt.wantErr {
t.Fatalf("CalculateCommission() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.expected {
t.Errorf("CalculateCommission() = %v, want %v", got, tt.expected)
}
})
}
}

Интеграционный тест с testcontainers:

GO
//go:build integration
func TestOrderRepository(t *testing.T) {
ctx := context.Background()
pg, err := postgres.RunContainer(ctx,
testcontainers.WithImage("postgres:16-alpine"),
postgres.WithDatabase("testdb"),
postgres.WithUsername("user"),
postgres.WithPassword("pass"),
)
if err != nil {
t.Fatal(err)
}
defer pg.Terminate(ctx)
connStr, _ := pg.ConnectionString(ctx)
db, _ := sql.Open("pgx", connStr)
// миграции, тесты репозитория
}

Как отвечать на собеседовании

Начни с пирамиды тестирования и того, как она применяется в Go. Подчеркни, что тесты - часть процесса, а не отдельная активность. Упомяни конкретные инструменты: testing, testcontainers, httptest, моки через интерфейсы. Расскажи про CI и code review как часть стратегии. Если спросят про покрытие - скажи, что это индикатор, а не цель, и приведи пример, когда высокое покрытие не гарантирует качество. Не углубляйся в детали реализации, если не просят - покажи понимание процесса и trade-off между скоростью и надёжностью тестов.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание уровней тестирования и их назначения.
  • Умение выбирать стратегию для конкретного сервиса.
  • Знание инструментов Go для тестирования.
  • Понимание роли CI/CD и code review в обеспечении качества.
  • Способность объяснить, почему тесты важны, а не просто "так принято".

Ключевое - видит ли кандидат тестирование как неотъемлемую часть разработки, а не как обязанность, которую нужно отбыть.

Типичные ошибки

  • Сведение тестирования только к unit-тестам, игнорирование интеграционных и e2e.
  • Тестирование реализации вместо поведения: проверка вызовов моков вместо результата.
  • Отсутствие проверки ошибок в тестах - тест проходит, даже если функция возвращает ошибку.
  • Слишком хрупкие тесты: привязаны к внутренним деталям, ломаются при рефакторинге.
  • Игнорирование параллельного выполнения тестов (t.Parallel()), что приводит к гонкам.
  • Использование реальных внешних сервисов в unit-тестах вместо моков - тесты становятся медленными и нестабильными.
  • Погоня за 100% покрытием без анализа, что именно покрыто.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью