👨💻
Строка, похожая на комментарий, которая молча сломала продВ одной команде была здоровая культура тестов. Каждый важный эндпоинт покрыт, CI не пускал PR в мердж, пока тесты не зелёные. Однажды утром прилетел инцидент на проде. Разработчик правил логику обработки ответа стороннего сервиса, прогнал тесты локально, получил 144 passed, отправил PR, CI стал зелёным, фикс уехал в прод. Через час прод лёг.
При разборе выяснилось, что нужный тест
TestCartActionHandler вообще не запускался. Не падал, не скипался, его просто не было в списке из 144 тестов. В первой строке файла, над объявлением пакета, стояла строка, которую все приняли за комментарий:
//go:build integration
Это и оказалось причиной. Дальше разберём, как одна такая строка выкидывает целый файл из сборки.
❓
Что такое build tagsBuild tag это директива в начале
.go файла, которая говорит тулчейну, включать ли файл в конкретную сборку. Работает как условный шлюз на уровне файла. Когда вы запускаете
go build или
go test, тулчейн проверяет ограничение каждого файла против текущего контекста сборки.
Выражение истинно, файл компилируется. Выражение ложно, файл исключается полностью, как будто его нет на диске.
Это не скип в рантайме, это исключение на этапе компиляции. Файл вообще не попадает в бинарь, поэтому ни одна тестовая функция из него не регистрируется и никакого вывода от него вы не увидите.
🛠
СинтаксисТег это однострочная директива в самом верху файла, до
package, до импортов:
//go:build <выражение>
Три правила, которые проверяет тулчейн. Директива стоит в первой строке (выше допустимы только пустые строки и другие
//go: директивы). Префикс ровно
//go:build, без пробела между
// и
go. Выражение булево, собирается из имён тегов через
&&,
||,
! и скобки.
🛠
Язык выраженийФайл с тегом попадёт в сборку, только если вы явно попросите через
-tags. Не попросили, файла как бы не существует.
//go:build tagname // истинно, когда задан -tags tagname
//go:build !tagname // истинно, когда tagname НЕ задан
//go:build tagA && tagB // оба заданы
//go:build tagA || tagB // задан любой из двух
//go:build (tagA || tagB) && !tagC // группировка скобками
Активируется тег через флаг
-tags у любой
go команды:
go build -tags linux ./...
go test -tags integration ./...
go run -tags debug main.go
go test -tags integration,gpu ./...
Флаг работает одинаково для
go build,
go test,
go run и
go vet. То, что вы передаёте, добавляется в активный набор тегов только для этого вызова и не сохраняется.
🛠
Где это нужноПлатформенные реализации. Один пакет, одна сигнатура функции, разный файл под каждую ОС. Компилятор сам подберёт нужный по
GOOS, без
switch runtime.GOOS и без мёртвого кода в бинаре:
//go:build linux
func watch(path string) { /* inotify */ }
//go:build darwin
func watch(path string) { /* kqueue */ }
Так устроена и стандартная библиотека. У
os.RemoveAll три отдельных файла, каждый со своим тегом под
unix,
windows и фолбэк на остальное.
Разделение unit и интеграционных тестов. Интеграционным часто нужна живая инфраструктура, юнит тестам нет. Тег прячет тяжёлые тесты, чтобы локально не поднимать базу ради простой проверки:
//go:build integration
package repository_test
func TestUserRepository_Create(t *testing.T) { ... } // ходит в реальный Postgres
Опциональные тяжёлые зависимости и debug инструментация работают так же. Под тегом
gpu лежит реализация на CUDA, без тега чистый Go фолбэк. Под тегом
debug живут дампы состояния, которые в прод бинарь не попадают вообще.
❓
Чем закончился инцидентФайл с тестом имел тег
//go:build integration, а команда
make test запускала
go test без
-tags integration. Файл молча исключался из прогона, и его тесты не запускались ни разу с момента старта проекта. Скелет теста скопировали из соседнего проекта, где
make test сознательно прокидывал нужный тег. Тот, кто копировал, принял первую строку за комментарий. Ревьюер тоже не заметил.
Таких файлов в кодовой базе нашлось ещё 13. После снятия тега и прогона набор показал 208 тестов вместо 144.
--- FAIL: TestHandlePaymentPartyResponse (0.31s)
--- FAIL: TestUserSignatureValidation (0.18s)
...
FAIL: 9 tests failed, 208 tests total
64 теста не выполнялись никогда, а среди 9 падений были баги, уже жившие в проде. Зелёный бейдж CI всё это время был ложноположительным.
➡️
Как чуть не сломался прод📍 Навигация:
Вакансии •
Задачи •
Собесы •
Канал в Max🐸
Библиотека Go-разработчика#GoDeep