Расскажите о подходах к тестированию на вашем последнем проекте на Go?

Ответ

На проекте мы выстроили многоуровневую стратегию тестирования для обеспечения качества и надежности нашего кода:

  1. Unit-тесты (Модульные тесты). Мы использовали стандартный пакет testing для написания тестов, покрывающих отдельные функции и методы. Основной упор делался на табличные тесты (table-driven tests), что позволяло легко проверять множество граничных случаев.

    func TestAdd(t *testing.T) {
        testCases := []struct {
            name string
            a, b int
            want int
        }{
            {"positive numbers", 1, 2, 3},
            {"zeroes", 0, 0, 0},
            {"negative and positive", -1, 1, 0},
        }
    
        for _, tc := range testCases {
            t.Run(tc.name, func(t *testing.T) {
                got := Add(tc.a, tc.b)
                if got != tc.want {
                    t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want)
                }
            })
        }
    }
  2. Интеграционные тесты. Проверяли взаимодействие нескольких компонентов системы. Например, связку "сервис-репозиторий-база данных". Для изоляции от внешних зависимостей (БД, сторонние API) мы активно использовали моки, в основном с помощью библиотеки testify/mock.

  3. E2E-тесты (End-to-End). Эти тесты эмулировали полные пользовательские сценарии, отправляя реальные HTTP-запросы к нашему API и проверяя ответы. Для этого использовались как тесты, написанные на Go с помощью стандартного HTTP-клиента, так и инструменты вроде Postman/Newman для автоматизации коллекций.

  4. CI/CD. Все тесты автоматически запускались в нашем CI/CD-пайплайне (например, GitHub Actions) при каждом коммите в pull request. Сборка не могла быть влита в основную ветку, если тесты проваливались.

Мы также следили за метрикой покрытия кода тестами (go test -cover), стремясь поддерживать её на уровне не ниже 80% для критически важных пакетов.