Покрытие кода тестами в Go: метрики, инструменты и реальные примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Покрытие кода тестами в Go: от «зелёной галочки» к реальному качеству

В мире Go-разработки фраза «у меня 95% покрытия» звучит гордо. Но часто за этой цифрой скрывается пустота: тесты проверяют только счастливый путь, а граничные случаи и ошибки остаются за бортом. В этой статье мы разберёмся, как измерять покрытие кода тестами в Go по-настоящему, какие метрики действительно важны, и какие инструменты помогут превратить тестирование из формальности в мощный инструмент контроля качества.

Мы не будем пересказывать документацию. Вместо этого — живые примеры, ловушки и практические советы для тех, кто пишет на Go и хочет спать спокойно.

Почему просто 80% — это плохо?

Допустим, у вас есть функция:

func ProcessOrder(amount float64, discount bool) (float64, error) {
    if amount <= 0 {
        return 0, errors.New("amount must be positive")
    }
    if discount {
        amount *= 0.9
    }
    return amount, nil
}

Вы написали один тест: ProcessOrder(100, true) → 90, nil. Покрытие? 100% строк. Но вы не проверили случай с amount <= 0 и случай без скидки. Формально — покрытие отличное. Реально — баги гарантированы. Вот почему метрики покрытия кода тестами в Go нужно рассматривать в связке с другими показателями.

Инструменты покрытия: от простого к сложному

1. Встроенный go test -cover

Самый простой способ узнать покрытие кода тестами в Go — флаг -cover:

go test -cover ./...

Вывод:

ok      myproject/orders    0.234s    coverage: 72.3% of statements

Но это лишь общая цифра. Чтобы увидеть, какие строки не покрыты, используйте -coverprofile:

go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

Откроется браузер с подсветкой: зелёное — покрыто, красное — нет. Это первый шаг к пониманию метрик покрытия Go.

2. go tool cover: режимы и нюансы

go tool cover поддерживает три режима:

  • set — была ли строка выполнена хоть раз (по умолчанию)
  • count — сколько раз выполнилась каждая строка
  • atomic — count, но с атомарными счётчиками для параллельных тестов

Для анализа покрытия тестами Golang в многопоточном коде используйте atomic, иначе рискуете получить некорректные счётчики из-за race condition.

go test -covermode=atomic -coverprofile=coverage.out ./...

3. Race detector — скрытое покрытие

Гонки данных — одна из главных проблем Go-кода. Даже если покрытие строк 100%, race detector может найти ошибки, которые тесты не заметили. Запускайте тесты с флагом -race:

go test -race -cover ./...

Важно: race detector замедляет тесты в 2-20 раз, но для критичных модулей это обязательная практика. Включайте его в CI для ключевых пакетов.

Метрики, которые действительно имеют значение

Процент покрытия строк — лишь вершина айсберга. Вот что нужно отслеживать:

1. Покрытие ветвлений (branch coverage)

Стандартный go test -cover считает строки, а не ветки. Для покрытия кода тестами Go на уровне ветвлений используйте gocov или go-test-coverage. Пример:

go get github.com/axw/gocov/gocov
gocov test ./... | gocov report

Вы увидите не только строки, но и количество непокрытых условий. Например, для функции с двумя if-ами — 4 ветки, из которых покрыты 2.

2. Покрытие мутациями (mutation testing)

Самый честный способ оценить качество тестов — мутационное тестирование Go. Инструмент (например, go-mutesting) вносит мелкие изменения в код (меняет > на <, убирает return) и проверяет, «убивают» ли ваши тесты эти мутации.

go get github.com/zimmski/go-mutesting/...
go-mutesting ./...

Если мутация выжила — тест не проверяет этот сценарий. Цель: мутационный балл > 80% для критических модулей.

3. Покрытие по пакетам и функциям

Не гонитесь за общим процентом. Лучше установите пороги для каждого пакета. Например:

  • Утилитарные функции: ≥ 90%
  • Бизнес-логика: ≥ 80%
  • HTTP-обработчики: ≥ 70% (из-за сложности тестирования)

Это даст реальные метрики покрытия Go, а не «среднюю температуру по больнице».

Практический пример: от 60% к 95% с умом

Рассмотрим реальный код. Допустим, у нас есть сервис расчёта доставки:

type DeliveryService struct {
    db      Database
    geo     GeoProvider
    logger  Logger
}

func (s *DeliveryService) CalculateCost(orderID string) (float64, error) {
    order, err := s.db.GetOrder(orderID)
    if err != nil {
        s.logger.Error("failed to get order", "id", orderID, "err", err)
        return 0, fmt.Errorf("get order: %w", err)
    }
    
    distance, err := s.geo.Distance(order.From, order.To)
    if err != nil {
        s.logger.Error("failed to calc distance", "err", err)
        return 0, fmt.Errorf("distance: %w", err)
    }
    
    // Базовый тариф
    cost := 10.0 + distance*0.5
    
    // Скидка для постоянных клиентов
    if order.IsLoyal {
        cost *= 0.9
    }
    
    // Ночной тариф
    if time.Now().Hour() >= 22 || time.Now().Hour() < 6 {
        cost *= 1.5
    }
    
    return cost, nil
}

Начальное покрытие — 60% (проверен только счастливый путь). Чтобы поднять до 95%, нужно:

  1. Тест на ошибку БД — mock для s.db.GetOrder возвращает ошибку.
  2. Тест на ошибку гео

Блоги

Рекомендаций по книгам