Avatar ·

Почему println! не выводит текст в юнит-тестах Rust?

Привет! Я начал изучать Rust и столкнулся со странной проблемой при написании юнит-тестов. Я хочу вывести отладочную информацию с помощью макроса `println!`, но в терминале ничего не появляется. Вот мой код: ```rust #[cfg(test)] mod tests { #[test] fn test_debug_output() { let value = 42; println!("Значение: {}", value); assert_eq!(value, 42); } } ``` Когда я запускаю тесты командой `cargo test`, я вижу только: ``` running 1 test test tests::test_debug_output ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` Никакого вывода `"Значение: 42"` нет. Я ожидал, что `println!` будет работать как в обычном коде. Почему так происходит? Как мне увидеть отладочный вывод во время тестирования? Я использую Rust 1.70.0, но пробовал и на более новых версиях — результат тот же. Я уже перепробовал: перезапускал терминал, чистил кэш cargo, проверял, что тест действительно запускается (он проходит). В документации написано, что `println!` выводит в stdout, но в тестах этого не происходит. Может быть, я что-то упускаю в настройках проекта? Или это особенность Rust? Буду благодарен за объяснение! <<>>

Почему println! молчит в тестах?

В Rust юнит-тесты по умолчанию перехватывают вывод в stdout (и stderr). Это сделано намеренно: тесты должны быть чистыми и не засорять консоль. Когда тест проходит, его stdout подавляется, чтобы не мешать чтению результатов. Если тест падает, вывод показывается автоматически — это помогает в отладке.

Как увидеть вывод?

Есть два основных способа:

1. Флаг --nocapture (самый частый)

Запустите тесты с этим флагом, и весь stdout будет выведен в реальном времени:

cargo test -- --nocapture

Или для одного теста:

cargo test test_debug_output -- --nocapture

Теперь вы увидите:

running 1 test
Значение: 42
test tests::test_debug_output ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

2. Использовать eprintln! для stderr

Вывод в stderr не перехватывается тестовым раннером:

#[cfg(test)]
mod tests {
    #[test]
    fn test_debug_output() {
        let value = 42;
        eprintln!("Значение: {}", value); // <- eprintln!
        assert_eq!(value, 42);
    }
}

Запустите обычный cargo test — вывод появится:

running 1 test
Значение: 42
test tests::test_debug_output ... ok

Но будьте осторожны: eprintln! выводит в stderr, а не в stdout. В большинстве случаев это нормально для отладки, но в CI-системах stderr может считаться ошибкой.

Почему так сделано?

  • Чистота вывода: при запуске сотен тестов вывод от каждого сделает консоль нечитаемой.
  • Параллелизм: тесты по умолчанию выполняются параллельно. Без перехвата вывод перемешался бы.
  • Фокус на результатах: тестовый раннер показывает только итог (ok/failed). Если тест упал — вы увидите и вывод, и панику.

Дополнительные советы

  • Используйте assert! и assert_eq! с понятными сообщениями — это лучше, чем println! в тестах.
  • Для сложной отладки используйте dbg!(value) — он выводит в stderr и возвращает значение:
#[test]
fn test_with_dbg() {
    let value = 42;
    let result = dbg!(value * 2); // выведет: [src/lib.rs:10] value * 2 = 84
    assert_eq!(result, 84);
}

Запуск с cargo test покажет вывод dbg!, так как он пишет в stderr.

Итог

Используйте cargo test -- --nocapture для временного просмотра stdout или переключитесь на eprintln!/dbg! для постоянного вывода. В "боевых" тестах старайтесь обходиться без отладочного вывода — это делает тесты более читаемыми и надёжными.

<<>>

Альтернативный подход: логирование через crate log

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

Добавьте в Cargo.toml:

[dev-dependencies]
log = "0.4"
env_logger = "0.10"

В тестах:

#[cfg(test)]
mod tests {
    use log::info;

    #[test]
    fn test_with_logging() {
        // Инициализация один раз для всех тестов
        let _ = env_logger::builder()
            .is_test(true) // важно для тестов
            .try_init();

        let value = 42;
        info!("Значение: {}", value);
        assert_eq!(value, 42);
    }
}

Запускайте с переменной окружения RUST_LOG:

RUST_LOG=info cargo test

Преимущества:

  • Вывод структурирован: [INFO] Значение: 42
  • Можно включать/выключать логи по модулям и уровням (error, warn, info, debug, trace)
  • Не требует флага --nocapture — логи выводятся всегда (если уровень позволяет)

Недостаток: нужно добавлять зависимость и инициализировать логгер. Для простых проектов это может быть избыточно.