Тесты в Go: с чего начать и дойти до первого зелёного прогона
Go поставляется с готовым тестовым фреймворком — не нужно ничего устанавливать. Разбираем, как написать первый тест, табличные тесты, вспомогательные функции и запустить всё одной командой, получив внятный отчёт о покрытии.

Что потребуется до начала
Убедитесь, что у вас установлен Go 1.21 или новее. Проверьте версию:
go version
# go version go1.22.4 linux/amd64 — или ваша
Если команда не найдена — установите Go с официальной страницы загрузки. Никаких дополнительных библиотек для базового тестирования не нужно: пакет testing входит в стандартную библиотеку.
Создайте каталог проекта и инициализируйте модуль:
mkdir calc && cd calc
go mod init example.com/calc
После этого шага у вас появится файл go.mod. Если он есть — всё готово.
Первый тест за пять минут
Код, который будем тестировать
Создайте файл calc.go:
package calc
// Sum возвращает сумму двух целых чисел.
func Sum(a, b int) int {
return a + b
}
Файл теста
Go находит тесты по простому соглашению: файл заканчивается на _test.go, функция начинается с Test и принимает единственный аргумент *testing.T. Создайте calc_test.go:
package calc
import "testing"
func TestSum(t *testing.T) {
got := Sum(2, 3)
want := 5
if got != want {
t.Errorf("Sum(2, 3) = %d, хотели %d", got, want)
}
}
Запуск
go test -v
Флаг -v включает подробный вывод. Должна появиться строка:
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
Если вместо PASS вы видите FAIL — перепроверьте, что оба файла лежат в одном каталоге и принадлежат одному пакету (package calc).
В Go нет отдельной команды «запустить один тест-файл». Вы всегда запускаете тесты пакета целиком —go test— и фильтруете по имени функции через-run.
Табличные тесты — стандарт в Go-проектах
Один кейс — это хорошо для проверки связки, но в реальных проектах нужны десятки входов. Копировать функцию TestSum для каждого набора аргументов — путь к дублированию. Вместо этого Go-сообщество использует table-driven tests: срез структур с входами и ожидаемым результатом.
package calc
import "testing"
func TestSumTable(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"положительные", 2, 3, 5},
{"с нулём", 0, 7, 7},
{"отрицательные", -1, -4, -5},
{"разные знаки", -3, 10, 7},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Sum(tc.a, tc.b)
if got != tc.want {
t.Errorf("Sum(%d, %d) = %d, хотели %d",
tc.a, tc.b, got, tc.want)
}
})
}
}
Метод t.Run создаёт подтест с собственным именем. В выводе go test -v каждый подтест отображается отдельной строкой — сразу видно, какой именно кейс упал.
Чтобы запустить только один подтест:
go test -v -run TestSumTable/с_нулём
Проверка: в выводе должен быть ровно один RUN и один PASS.
Покрытие кода
Go умеет считать покрытие из коробки — без сторонних инструментов:
go test -cover
Вывод покажет процент покрытых строк пакета. Для визуального отчёта в браузере:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Откроется HTML-страница, где зелёным подсвечены выполненные строки, красным — пропущенные. Это самый быстрый способ найти ветки, которые тесты не трогают.
| Команда | Что делает | Когда использовать |
|---|---|---|
go test | Запускает все тесты текущего пакета | Быстрая проверка после правки |
go test ./... | Запускает тесты во всех пакетах проекта | Перед коммитом |
go test -v -run Имя | Запускает тесты, имя которых содержит подстроку | Отладка конкретного кейса |
go test -cover | Показывает процент покрытия | Оценка полноты тестов |
go test -race | Включает детектор гонок | Код с горутинами |
go test -count=1 | Отключает кеширование результатов | Когда нужен честный прогон |
Вспомогательные функции и t.Helper
Когда проверка повторяется в нескольких тестах, вынесите её в отдельную функцию. Чтобы при падении Go показывал строку вызова, а не строку внутри хелпера, пометьте функцию вызовом t.Helper():
package calc
import "testing"
// assertEqual сравнивает got и want, при расхождении фейлит тест.
func assertEqual(t *testing.T, got, want int) {
t.Helper() // без этого номер строки в отчёте укажет сюда, а не на вызов
if got != want {
t.Errorf("получили %d, хотели %d", got, want)
}
}
func TestSumHelper(t *testing.T) {
assertEqual(t, Sum(1, 1), 2)
assertEqual(t, Sum(-1, 1), 0)
}
Проверка: сломайте ожидаемое значение, запустите go test -v — номер строки в ошибке должен указывать на вызов assertEqual в TestSumHelper, а не на строку с t.Errorf внутри хелпера.
Сноска: t.Helper() — метод *testing.T, появившийся в Go 1.9. Он говорит тест-раннеру: «эта функция — обёртка, при ошибке показывай вызывающую строку». Без него отладка табличных тестов с общими проверками превращается в угадывание, какой именно кейс провалился.
Если не получилось
no test files— файл теста не заканчивается на_test.goили лежит в другом каталоге. Проверьте имя файла и текущую директорию.cannot find package— вы не выполнилиgo mod init. Безgo.modGo не понимает, какой модуль тестирует.undefined: Sum— пакет вcalc.goиcalc_test.goразличается. Оба файла должны начинаться сpackage calc.- Тесты проходят, но
go testничего не выводит — результат закеширован. Добавьте-count=1, чтобы запустить заново. FAILбез понятного сообщения — вы используетеt.Fatalилиt.Errorбез форматной строки. Всегда передавайте описание: что вызвали, что получили, что ожидали.
Что дальше
После первого зелёного прогона стоит разобраться с тремя вещами:
- Бенчмарки — функции вида
BenchmarkXxx(b *testing.B), запуск черезgo test -bench=.. Позволяют измерить производительность без сторонних инструментов. - Детектор гонок — флаг
-race. Если в проекте есть горутины, запускайте с ним хотя бы в CI: он ловит data race, которые воспроизводятся нестабильно. - Пакет
testify— сторонняя библиотека с привычнымиassertиrequire. Стандартная библиотека Go намеренно не включает assert-функции, но в крупных проектахtestifyэкономит десятки строк. Устанавливается черезgo get github.com/stretchr/testify.
Встройте go test ./... в CI-пайплайн — даже минимальный набор тестов, запускаемый при каждом пуше, ловит регрессии раньше, чем ручной прогон.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Официальная документация пакета testing — Go, How to Write Go Code — раздел Testing (Go Wiki)
Частые вопросы
Обязательно ли файл теста называть так же, как тестируемый файл, только с суффиксом _test?
Можно ли тестировать неэкспортируемые (маленькая буква) функции?
Что делать, если go test проходит локально, но падает в CI?
Как запустить тесты только в одном файле, а не во всём пакете?
У меня другая версия Go (1.18 / 1.20) — примеры из статьи сработают?
Нужно ли стремиться к 100% покрытия?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.