Программирование GoТестированиеGo-разработчик, пишущий unit-тесты

На CI успешный unit тест не показывает диагностические сообщения через t.Log. Как устроен вывод таких сообщ...

На CI успешный unit-тест не показывает диагностические сообщения через t.Log. Как устроен вывод таких сообщений и когда они становятся видимыми?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

т.Log привязывает сообщение к конкретному тесту или subtest. По умолчанию сообщения успешных тестов обычно не выводятся, но становятся видимыми при verbose-режиме; сообщения упавшего теста выводятся вместе с его результатом. Это делает диагностику структурированной и уменьшает шум в обычном запуске.

Исторический контекст

Пакет testing был создан как стандартный единый механизм запуска тестов, сбора результатов и диагностического вывода. Обычная печать в stdout плохо связывает сообщение с конкретным тестом, особенно при параллельном выполнении, поэтому тестовый API предоставляет Log и Logf.

Такой подход решает две исходные проблемы: скрывает лишний вывод успешных проверок и сохраняет диагностический контекст при ошибке. В результате CI показывает подробности именно для проблемных тестов, не превращая каждый успешный запуск в длинный журнал.

Постановка проблемы

Если использовать fmt.Println, сообщение не выражает намерение теста и не привязано к его объекту testing.T. При параллельном выполнении строки разных тестов могут перемешиваться, а при успешном запуске часть stdout может быть скрыта или отображаться не так, как ожидает автор теста.

т.Log также не является безусловным способом напечатать сообщение: для успешного теста его вывод зависит от режима запуска. Поэтому лог нельзя использовать как обязательный побочный эффект, например как сигнал для другой части теста или CI-скрипта.

Подробное решение

т.Log и т.Logf записывают диагностику в контекст текущего testing.T. Если тест завершился неуспешно, его лог показывается в отчёте. Для просмотра логов успешных тестов включают подробный режим запуска тестов, например через флаг -test.v у тестового бинарника или соответствующий режим go test.

Минимальный пример:

package calc import "testing" func TestAdd(t *testing.T) { got := 2 + 2 t.Logf("получен результат: %d", got) if got != 4 { t.Fatalf("got %d, want 4", got) } }

В обычном запуске успешного теста строка t.Logf может не попасть в итоговый вывод. В verbose-режиме она будет показана. Если проверка завершится через Errorf или Fatalf, диагностическое сообщение будет доступно в отчёте об этом тесте.

Главное ограничение: т.Log предназначен для диагностики, а не для проверки результата и не для синхронизации горутин. Для обязательного результата нужно использовать утверждение с Error, Fatal или явную передачу данных через безопасный механизм синхронизации.

Ситуация из практики

В CI команда видела только название упавшего subtest, но не знала, какой входной набор привёл к ошибке. Разработчик добавил fmt.Printf внутри цикла table-driven теста. Локально это помогало, однако при параллельных subtests строки перемешивались, а успешные прогоны создавали большой шум.

Рассматривались два варианта. Оставить fmt.Printf проще, но он не связан с объектом теста и плохо масштабируется при параллельном запуске. Всегда включать verbose-режим информативно, но увеличивает объём CI-логов для всего набора тестов.

Выбрали t.Logf с именем кейса и входными параметрами. При падении CI показывал диагностику только проблемного subtest, а для локального расследования включался verbose-режим. Это сохранило читаемость отчёта и корректную привязку сообщений к тестам.

Что кандидаты часто упускают

1. Вопрос: Можно ли считать отсутствие строки t.Log доказательством того, что код до неё не дошёл?

Ответ: Нет. Для успешного теста сообщение может быть скрыто обычным режимом вывода. Отсутствие строки в журнале не означает, что вызов t.Log не выполнялся; нужно проверить результат теста, включить verbose-режим или использовать отладчик и другие средства диагностики.

2. Вопрос: Чем t.Log принципиально лучше fmt.Println в параллельных subtests?

Ответ: t.Log записывает сообщение в контекст конкретного теста, поэтому тестовый фреймворк может показать его рядом с результатом соответствующего subtest. fmt.Println пишет в общий поток вывода: сообщения разных горутин могут перемешиваться, а их принадлежность к тесту приходится восстанавливать вручную. Это не делает t.Log средством синхронизации, но делает диагностику структурированной.

3. Вопрос: Можно ли заменить t.Log на t.Fatal, чтобы гарантированно увидеть диагностическое сообщение?

Ответ: Только если обнаруженная ситуация действительно делает продолжение теста бессмысленным. t.Fatal завершает текущий тест через остановку его выполнения, поэтому это не универсальный способ логирования. Для обычной диагностики следует использовать t.Log, а для нарушения условия — t.Error или t.Fatal в зависимости от того, может ли текущий сценарий безопасно продолжаться.