Avatar ·

Как вывести текст в консоль при тестировании в Go (пакет testing)?

Привет! Я начал изучать Go и пишу тесты для своих функций. Использую стандартный пакет `testing`. Когда я запускаю тесты через `go test`, мне нужно видеть промежуточные результаты или отладочную информацию в консоли. Например, я хочу проверить, что переменная имеет правильное значение внутри теста. Я пробовал использовать `fmt.Println()` внутри тестовой функции, но вывод не отображается. Вот мой код: ```go func TestSum(t *testing.T) { result := Sum(2, 3) fmt.Println("Result is:", result) // Не вижу этот вывод! if result != 5 { t.Errorf("Expected 5, got %d", result) } } ``` Когда я запускаю `go test`, я вижу только `PASS` или `FAIL`, но не вижу строку "Result is: 5". Почему так происходит? Как правильно выводить отладочную информацию в Go-тестах? Нужно ли использовать что-то вместо `fmt.Println`? Или есть специальные методы в пакете `testing` для логирования? Буду благодарен за примеры и объяснение! Я хочу понимать, как отлаживать тесты, особенно когда они сложные. <<>>

Почему fmt.Println не работает в тестах Go?

Вы столкнулись с особенностью работы Go-тестов. По умолчанию go test выводит только итоговый результат (PASS/FAIL) и сообщения об ошибках. Вывод через fmt.Println подавляется, чтобы не засорять консоль при прогоне сотен тестов. Это сделано намеренно — тесты должны быть чистыми и предсказуемыми.

Однако есть несколько способов увидеть отладочную информацию. Рассмотрим их от простого к продвинутому.

Способ 1: Флаг -v (verbose)

Самый простой способ — запустить тесты с флагом -v (verbose, подробный режим). Тогда весь вывод, включая fmt.Println, будет показан:

go test -v

Теперь ваш код заработает:

func TestSum(t *testing.T) {
    result := Sum(2, 3)
    fmt.Println("Result is:", result) // Будет видно с флагом -v
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

Вывод станет таким:

=== RUN   TestSum
Result is: 5
--- PASS: TestSum (0.00s)
PASS

Недостаток: нужно всегда помнить про флаг -v. Если забудете — вывод пропадёт.

Способ 2: Метод t.Log() и t.Logf() (рекомендуемый)

Пакет testing предоставляет специальные методы для логирования: t.Log() и t.Logf(). Их главное преимущество — они работают как fmt.Print/fmt.Printf, но:

  • Вывод показывается только при запуске с флагом -v
  • Вывод показывается автоматически, если тест упал (даже без -v)
  • Логи привязываются к конкретному тесту, что удобно при параллельном запуске

Пример:

func TestSum(t *testing.T) {
    result := Sum(2, 3)
    t.Logf("Result is: %d", result) // Логирование, привязанное к тесту
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

Если тест упадёт, вы увидите:

=== RUN   TestSum
    main_test.go:10: Result is: 4
--- FAIL: TestSum (0.00s)
    main_test.go:12: Expected 5, got 4
FAIL

Обратите внимание: лог появился, хотя мы не использовали -v. Это очень удобно для отладки упавших тестов.

Способ 3: Принудительный вывод через t.Error() или t.Fatal()

Если вам нужно гарантированно показать информацию, даже без -v, можно использовать методы, которые помечают тест как упавший:

  • t.Error(args...) — логирует ошибку и продолжает выполнение теста
  • t.Fatal(args...) — логирует ошибку и немедленно останавливает тест

Пример:

func TestSum(t *testing.T) {
    result := Sum(2, 3)
    t.Log("Проверяем результат...")
    if result != 5 {
        t.Errorf("Ожидали 5, получили %d", result) // Покажет это сообщение всегда
    }
    // Если хотите просто показать значение без ошибки — не используйте Error/Fatal
}

Важно: Не злоупотребляйте этим! Используйте t.Error только для настоящих ошибок, иначе тест будет считаться упавшим.

Способ 4: os.Stdout и кастомный логгер (для продвинутых)

Если вам нужно перенаправить вывод в отдельный файл или обработать его, можно использовать стандартный пакет log или напрямую писать в os.Stdout. Но это уже излишество для обычных тестов.

Резюме

  • Используйте t.Log() / t.Logf() для отладочного вывода — это идиоматичный способ в Go
  • Запускайте тесты с go test -v, чтобы видеть все логи
  • Не используйте fmt.Println — он не привязан к тесту и путает вывод
  • Для сообщений об ошибках используйте t.Error / t.Fatal

Теперь вы сможете эффективно отлаживать свои тесты!

<<>>

Альтернативный подход: Использование testing.T.Cleanup() для логирования после теста

Иногда нужно вывести сводную информацию после завершения теста, независимо от того, упал он или прошёл. Для этого можно использовать метод t.Cleanup(), который регистрирует функцию, выполняющуюся после завершения теста:

func TestSum(t *testing.T) {
    result := Sum(2, 3)
    
    t.Cleanup(func() {
        t.Logf("Очистка после теста. Результат был: %d", result)
    })
    
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

Этот метод полезен, когда вы хотите гарантированно вывести финальное состояние или освободить ресурсы (закрыть файлы, соединения). Логи из Cleanup также подчиняются правилам: показываются с -v или при падении теста.

Дополнительный совет: Если вы пишете табличные тесты (table-driven tests), используйте t.Run() для подтестов. Каждый подтест может иметь свои логи:

func TestSumTable(t *testing.T) {
    tests := []struct{
        a, b, expected int
    }{
        {2, 3, 5},
        {0, 0, 0},
        {-1, 1, 0},
    }
    
    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
            result := Sum(tt.a, tt.b)
            t.Logf("Result: %d", result)
            if result != tt.expected {
                t.Errorf("got %d, want %d", result, tt.expected)
            }
        })
    }
}

Такой подход делает вывод максимально структурированным: каждый подтест выводится отдельно, и логи не смешиваются.