Пакет
testing в Go покрывает большинство задач без сторонних библиотек. Но сам по себе инструмент не гарантирует качество тестов. Ниже собраны практики, которые помогают писать тесты понятнее, надёжнее и проще в поддержке.1. Пишите table-driven тесты
Это идиоматический подход в Go. Все сценарии описываются в слайсе структур и прогоняются в цикле через
t.Run. Добавить новый кейс — одна строка. Сразу видно, что покрыто, а что нет:tests := []struct {
name string
input int
expected int
wantErr bool
}{
{"positive", 5, 25, false},
{"zero", 0, 0, false},
{"negative", -1, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Square(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
if result != tt.expected {
t.Errorf("got %d, want %d", result, tt.expected)
}
})
}2. Используйте t.Run для подтестов
Подтесты позволяют запускать конкретный сценарий по имени. Это экономит время при отладке, когда не нужно гонять весь набор:
go test -run TestSquare/negative
3. Помечайте хелперы через t.Helper()
Без
t.Helper() стек ошибки укажет на строку внутри хелпера. С ним — на строку вызова в тесте:func assertEqual(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}4. Мокайте зависимости через интерфейсы
Определяете интерфейс, в проде подставляете реальную реализацию, в тестах — мок:
type Storage interface {
Get(id int) (*Item, error)
}
type MockStorage struct {
items map[int]*Item
}
func (m *MockStorage) Get(id int) (*Item, error) {
if item, ok := m.items[id]; ok {
return item, nil
}
return nil, errors.New("not found")
}5. Тестируйте поведение, а не реализацию
Если тест ломается при рефакторинге внутренней логики без изменения внешнего контракта — это плохой тест. Начинайте с покрытия экспортируемых функций. Один тест проверяет одну вещь.
6. Давайте тестам понятные имена
Имя теста должно описывать сценарий и ожидаемый результат. При падении вы должны понять, что сломалось, не открывая код.
7. Запускайте тесты с флагом -race
Детектор гонок ловит проблемы конкурентного доступа, которые почти невозможно найти вручную.
go test -race ./...
8. Не гонитесь за 100% покрытия
Высокий процент не гарантирует отсутствие багов. Покрывайте критическую логику и граничные случаи. Качество тестов важнее количества.
9. Используйте TestMain для дорогого setup
Если подготовка окружения занимает время, вынесите её в
TestMain. Она запускается один раз на весь пакет, а не перед каждым тестом:func TestMain(m *testing.M) {
setup()
code := m.Run()
teardown()
os.Exit(code)
}10. Выбирайте инструменты под задачу
Стандартного
testing хватает для большинства случаев. testify добавляет удобные ассерты и моки. httptest закрывает тестирование HTTP-хендлеров. testcontainers-go помогает с интеграционными тестами через Docker. Не тащите лишнего.➡️ Нашу рассылку тестировать не нужно, её нужно читать
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction