Test Coverage в Go: измеряем и повышаем качество кода

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

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

Начать курс

Как измерить test coverage в Go и не сойти с ума

Вы написали тесты для своего Go-приложения. Чувствуете себя супергероем? Правильно делаете. Но есть один нюанс: сколько именно кода покрыто тестами? Без ответа на этот вопрос ваша уверенность — просто гадание на кофейной гуще.

Вопрос "How to measure test coverage in Go" — один из самых популярных на Stack Overflow (десятки тысяч просмотров). И это не случайно. Разработчики хотят не просто писать тесты, а понимать, насколько они эффективны. В этом туториале мы разберем все способы измерения test coverage в Go: от базовых команд до продвинутых техник с профилями и HTML-отчетами.

Что такое test coverage и зачем его измерять?

Test coverage (покрытие кода тестами) — это метрика, показывающая, какой процент вашего исходного кода выполняется во время тестов. Выражается в процентах. 100% coverage — это не всегда Святой Грааль, но стремиться к высоким показателям стоит.

Зачем это нужно?

  • Контроль качества: вы видите, какие участки кода остались без тестов
  • Уверенность в рефакторинге: высокое покрытие снижает риск что-то сломать
  • CI/CD метрики: можно настроить порог coverage и не пускать код с низким покрытием
  • Документация: тесты — это живая документация, а coverage показывает, насколько она полная

Подвох: coverage ≠ качество тестов

Помните: 100% coverage не гарантирует отсутствие багов. Можно написать тесты, которые просто вызывают функции, но ничего не проверяют. Coverage — это количественная метрика. Качество тестов — качественная. Используйте оба подхода.

Базовое измерение: go test -cover

Начнем с самого простого. У Go есть встроенная поддержка coverage прямо в инструменте go test. Давайте создадим простой проект и посмотрим, как это работает.

Создайте файл math.go:

package main

func Add(a, b int) int {
    return a + b
}

func Subtract(a, b int) int {
    return a - b
}

func Multiply(a, b int) int {
    return a * b
}

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, nil // Ошибка: должно быть return 0, errors.New("division by zero")
    }
    return a / b, nil
}

А теперь — тест (math_test.go):

package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("expected 5, got %d", result)
    }
}

func TestSubtract(t *testing.T) {
    result := Subtract(10, 4)
    if result != 6 {
        t.Errorf("expected 6, got %d", result)
    }
}

// Намеренно не тестируем Multiply и Divide

Теперь запустим тесты с флагом -cover:

go test -cover

Вывод будет примерно таким:

ok      example/math    0.002s  coverage: 50.0% of statements

Все верно — покрыто 50% кода, потому что Multiply и Divide не тестируются. Но это лишь общая цифра. А что, если мы хотим увидеть детальный профиль покрытия?

Профиль покрытия: go test -coverprofile

Флаг -coverprofile сохраняет детальную информацию о покрытии в файл:

go test -coverprofile=coverage.out

Теперь в папке появился файл coverage.out. Откройте его — это текстовый формат, где каждая строка показывает, была ли выполнена конкретная инструкция кода. Но читать его вручную — скучно. Давайте перейдем к визуализации.

HTML-отчет: go tool cover -html

Самый удобный способ анализа coverage — HTML-отчет. Он подсвечивает выполненный код зеленым, а невыполненный — красным.

go tool cover -html=coverage.out -o coverage.html

Откройте coverage.html в браузере. Вы увидите:

  • Зеленые строки — код, который выполняется хотя бы в одном тесте
  • Красные строки — код без тестов
  • Серые строки — неисполняемые строки (комментарии, пустые строки)

Для нашего примера Multiply и Divide будут красными. А еще вы заметите, что if b == 0 внутри Divide тоже красный — мы не тестировали случай деления на ноль.

Продвинутые техники: флаги -covermode и -coverpkg

Режимы покрытия: -covermode

Go поддерживает три режима покрытия:

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

Режим count особенно полезен, если вы хотите убедиться, что код в циклах выполняется несколько раз:

go test -covermode=count -coverprofile=coverage.out

В HTML-отчете для режима count цвет будет меняться в зависимости от количества выполнений — от зеленого (мало) до синего (много).

Покрытие для определенных пакетов: -coverpkg

Иногда нужно измерить покрытие не только для текущего пакета, но и для зависимых. Например, вы тестируете пакет handlers, а хотите увидеть покрытие в services и models:

go test -coverprofile=coverage.out -coverpkg=./services,./models ./handlers

Это особенно полезно в больших проектах с микросервисной архитектурой.

Интеграция в CI/CD: автоматический контроль coverage

Ручной запуск — это хорошо, но в современной разработке coverage должен проверяться автоматически при каждом пуше. Рассмотрим интеграцию с GitHub Actions.

Создайте файл .github/workflows/test.yml:

name: Go tests with coverage

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    - name: Run tests with coverage
      run: go test -coverprofile=co                

Блоги

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