为什么在Rust单元测试中无法使用println!?

596
我已经实现了以下的方法和单元测试:
use std::fs::File;
use std::path::Path;
use std::io::prelude::*;

fn read_file(path: &Path) {
    let mut file = File::open(path).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    println!("{contents}");
}

#[test]
fn test_read_file() {
    let path = &Path::new("/etc/hosts");
    println!("{path:?}");
    read_file(path);
}

我以这种方式运行单元测试:
rustc --test app.rs; ./app

我也可以用这个来运行。
cargo test

我收到一条回复消息说测试通过了,但是屏幕上却没有显示println!。为什么呢?

我觉得这很直观,因此我在GitHub上开了一个问题(Issue #10777),建议改变这种行为。链接:https://github.com/rust-lang/cargo/issues/10777#issue-1277499859 - Evan Carroll
我觉得这个很不直观,所以我在 GitHub 上开了一个 Issue #10777 来建议改变这个行为。 - Evan Carroll
9个回答

671

这是因为Rust测试程序会隐藏成功测试的标准输出,以使测试结果更加整洁。您可以通过向测试二进制文件或cargo test传递--nocapture选项来禁用此行为(但在这种情况下,请注意在--之后传递):

#[test]
fn test() {
    println!("Hidden output")
}

调用测试:

% rustc --test main.rs; ./main

running 1 test
test test ... ok

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

% ./main --nocapture

running 1 test
Hidden output
test test ... ok

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

% cargo test -- --nocapture

running 1 test
Hidden output
test test ... ok

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

如果测试失败,无论是否存在此选项,其标准输出都将被打印出来。


20
您提到要在cargo test命令中添加--nocapture选项,但是我使用最新的通过rustup.sh安装的夜版版本,cargo并未识别此标志。您确定它可以工作吗? - Jim Garrison
65
@JimGarrison,确实存在这个问题(https://github.com/rust-lang/cargo/issues/296)。同时你可以使用 cargo test -- --nocapture 命令来解决,这应该可以正常工作。 - Vladimir Matveev
5
谢谢!虽然与这个问题无关,但这也帮助我弄清楚了如何让 cargo test [--] --bench 正常工作! - Jim Garrison
12
@Nashenas,这个选项名叫做nocapture,而不是no-capture - Vladimir Matveev
23
“--nocapture”在2018版本中仍然有效。另一种选择是使用“--show-output”,它可以以更易于观察的格式组织输出结果。 - L. F.
显示剩余3条评论

172

简而言之

$ cargo test -- --nocapture

使用以下代码:

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PieceShape {
    King, Queen, Rook, Bishop, Knight, Pawn
}

fn main() {
    println!("Hello, world!");
}

#[test]
fn demo_debug_format() {
    let q = PieceShape::Queen;
    let p = PieceShape::Pawn;
    let k = PieceShape::King;
    println!("q={:?} p={:?} k={:?}", q, p, k);
}

接下来运行以下命令:

 $ cargo test -- --nocapture

您应该看到

Running target/debug/chess-5d475d8baa0176e4

running 1 test
q=Queen p=Pawn k=King
test demo_debug_format ... ok

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

1
cargo test -- --no-capture 不再起作用。我收到以下错误:thread '<main>' panicked at '"Unrecognized option: \'no-capture\'."', ../src/libtest/lib.rs:249 - Nashenas
我不知道这个问题 https://github.com/rust-lang/cargo/issues/1377 是否是问题的根源? - superlogical
15
在之前的评论中已经指出,选项是“--nocapture”,而不是“--no-capture”。然而,考虑到我们通常遇到的大多数命令行约定,这是一个非常明显的错误。我在rust 1.1(cargo 0.2.0)中完全按照这个答案中描述的使用了这个选项,它的效果与广告完全一致。 - Glenn McAllister

66
前言:本答案反映了Rust 1.41的变化(根据CHANGELOG,现在可以将--show-output参数传递给测试二进制文件以打印成功测试的输出)。
如L. F.所提到的,--show-output是正确的方法。
$ cargo test -- --show-output

其他显示标志在cargo test的display-options文档中有提到

16
注意:--show-output 仅会打印 成功 测试的标准输出。 - zingi
1
这很有帮助。CL 上的奇怪额外符号让我感到困惑。 我只需要运行一个测试并显示输出。看起来像这样: $ cargo test [test_name] -- --show-output - raddevus
是的,如果我没记错的话,额外的奇怪的 -- 是为了控制下一个参数传递给 cargo test - Raphaël Duchaîne
@zingi 这不是真的。根据文档,它也会显示失败测试的输出。 - Makich
3
我刚刚测试了一下,确实看起来已经改变了。--show-output现在会打印成功和失败测试的标准输出(v1.68.2)。 - zingi
目前,无论此标志如何设置,失败的测试都会打印其输出。 - Zoey Hewll

27

要在println!()中包含打印输出并保留测试结果的颜色,请在cargo test中使用colornocapture标志。

$ cargo test -- --color always --nocapture

(货物版本: 0.13.0 夜版)


12

在测试过程中,标准输出不会被显示。不要使用文本消息进行测试,而是使用assert!assert_eq!fail!。Rust的单元测试系统可以理解这些内容,但无法理解文本消息。

你编写的测试即使出现问题也会通过。让我们看看原因:

read_to_end的签名是fn read_to_end(&mut self) -> IoResult<Vec<u8>>

它返回一个IoResult来表示成功或错误。这只是一个类型定义为Result,其错误值为IoError。如何处理错误由您决定。在这种情况下,我们希望任务失败,这可以通过在Result上调用unwrap来完成。

这将有效:

let contents = File::open(&Path::new("message.txt"))
    .read_to_end()
    .unwrap();

unwrap 不应过度使用。


真实信息,但不是对 OP 的答案。 - BobHy
我认为这是Rust防止我们自己搞砸的方式。第二种形式的assert正是日志记录应该做的事情,但是方法不同。不必在一切正常时费力地编写10K+个日志,而是在出现问题时进行编写。std::module_path宏也可能会有用。 - dobhareach

5
请注意,现代解决方案 (cargo test -- --show-output) 不适用于在函数文档字符串中定义的Markdown代码围栏中的doctest。只有在具体的#[test]块中执行的println!等语句才会被尊重。

4

很可能测试输出是由测试框架捕获的,而不是被打印到标准输出。使用 cargo test 运行测试时,每个测试的输出都会被捕获并仅在测试失败时显示。如果您想查看测试的输出,可以在使用 cargo test 运行测试时使用 --nocapture 标志。像这样:

cargo test -- --nocapture

或者您可以在测试函数中使用println!宏来将输出打印到标准输出。像这样:

 #[test]
 fn test_read_file() {
    let path = &Path::new("/etc/hosts");
    println!("{:?}", path);
    read_file(path);
    println!("The test passed!");
}

1

如果您想在每次文件更改时显示打印输出并运行测试:

sudo cargo watch -x "test -- --nocapture"

sudo 可能根据您的设置是可选的。


最好在这个答案中不提到sudo,因为它是一个无关的问题,与这个问题无关。 - undefined

0

为什么呢?我也不知道,但有一个小技巧:eprintln!("将会在{}", "测试中打印")


1
对于我来说,eprintln!()在成功的测试中无法正常工作(cargo/rustc 1.69.0 (2023-04-12))。 - Mathieu CAROFF
1
不,cargo test 默认情况下会捕获标准输出和标准错误输出(当所有测试通过时)。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接