开发环境
- Windows 10
- Rust 1.72.1
- VS Code 1.82.2
项目工程
这里继续沿用上次工程rust-demo
控制测试如何运行
正如cargo run编译您的代码,然后运行生成的二进制文件一样,cargo test在测试模式下编译您的代码,然后运行生成的测试二进制文件。cargo test生成的二进制文件的默认行为是并行运行所有测试,并捕获测试运行期间生成的输出,从而防止显示输出,并使读取与测试结果相关的输出变得更容易。但是,您可以指定命令行选项来更改此默认行为。
一些命令行选项转到cargo test,一些转到结果测试二进制文件。为了区分这两种类型的参数,您列出了连接到cargo test的参数,后跟分隔符 --,然后是连接到测试二进制的参数。运行cargo test --help 显示您可以使用cargo test的选项,运行cargo test --help显示您可以在分隔符后使用的选项。
并行或连续运行测试
当您运行多个测试时,默认情况下它们使用线程并行运行,这意味着它们运行得更快,您得到反馈也更快。因为测试是同时运行的,所以您必须确保您的测试不依赖于彼此或者任何共享状态,包括共享环境,比如当前的工作目录或者环境变量。
例如,假设您的每个测试运行一些代码,这些代码在磁盘上创建一个名为test-output.txt的文件,并将一些数据写入该文件。然后,每个测试读取该文件中的数据,并断言该文件包含特定的值,该值在每个测试中是不同的。因为测试是同时运行的,所以一个测试可能会在另一个测试写入和读取文件之间的时间内覆盖文件。第二个测试将会失败,不是因为代码不正确,而是因为测试在并行运行时相互干扰。一个解决方案是确保每个测试写入不同的文件;另一个解决方案是一次运行一个测试。
如果您不想并行运行测试,或者如果您想要对使用的线程数量进行更细粒度的控制,那么您可以向测试二进制文件发送- test-threads标志和想要使用的线程数量。看一下下面的例子:
$ cargo test -- --test-threads=1
我们将测试线程的数量设置为1,告诉程序不要使用任何并行性。使用一个线程运行测试将比并行运行测试花费更长的时间,但是如果它们共享状态,测试将不会相互干扰。
显示功能输出
默认情况下,如果测试通过,Rust的测试库会捕获任何打印到标准输出的内容。例如,如果我们调用println!在测试中,如果测试通过,我们将看不到println!在终端中的输出;我们只会看到表示测试通过的那一行。如果一个测试失败了,我们将看到输出到标准输出中的内容以及失败消息的其余部分。
举例来说,示例11-10有一个简单的函数,它打印参数值并返回10,还有一个通过的测试和一个失败的测试。
文件名:src/lib.rs
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {}", a);
10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_test_will_pass() {
let value = prints_and_returns_10(4);
assert_eq!(10, value);
}
#[test]
fn this_test_will_fail() {
let value = prints_and_returns_10(8);
assert_eq!(5, value);
}
}
示例11-10:测试调用println!的函数
当我们用cargo test运行这些测试时,我们将看到以下输出:
$ cargo test
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
请注意,在这个输出中,我们看不到I got the value 4,这是通过的测试运行时打印的内容。该输出已被捕获。失败的测试的输出:I got the value 8,出现在测试总结输出部分,它也显示了测试失败的原因。
如果我们还想看到通过测试的打印值,我们可以告诉Rust用- show-output显示成功测试的输出。
$ cargo test -- --show-output
当我们用- show-output标志再次运行示例11-10中的测试时,我们会看到下面的输出:
$ cargo test -- --show-output
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok
successes:
---- tests::this_test_will_pass stdout ----
I got the value 4
successes:
tests::this_test_will_pass
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
按名称运行测试子集
有时,运行一个完整的测试套件可能需要很长时间。如果您正在处理某个特定领域的代码,您可能希望只运行与该代码相关的测试。您可以通过将想要运行的测试的名称作为参数传递给cargo test来选择要运行的测试。
为了演示如何运行测试子集,我们将首先为add_two函数创建三个测试,如示例11-11所示,并选择要运行的测试。
文件名:src/lib.rs
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_two_and_two() {
assert_eq!(4, add_two(2));
}
#[test]
fn add_three_and_two() {
assert_eq!(5, add_two(3));
}
#[test]
fn one_hundred() {
assert_eq!(102, add_two(100));
}
}
示例11-11:三个不同名称的三个测试
如果我们运行测试而不传递任何参数,正如我们前面看到的,所有的测试将并行运行:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
运行单一测试
我们可以将任何测试函数的名称传递给cargo test,以便只运行该测试:
$ cargo test one_hundred
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.69s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::one_hundred ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
只有名为one_hundred的测试运行;其他两项测试都不符合这个名字。测试输出让我们知道我们有更多的测试没有运行,通过在最后显示2 filtered out。
我们不能以这种方式指定多个测试的名称;将只使用cargo test的第一个值。但是有一种方法可以运行多个测试。
过滤以运行多个测试
我们可以指定测试名称的一部分,任何名称与该值匹配的测试都将运行。例如,因为我们的两个测试名称包含add,所以我们可以通过运行cargo test add:
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
该命令运行名称中带有add的所有测试,并过滤掉名为one_hundred的测试。还要注意,测试所在的模块成为测试名称的一部分,因此我们可以通过过滤模块名称来运行模块中的所有测试。
除非特别要求,否则忽略一些测试
有时一些特定的测试执行起来非常耗时,因此您可能希望在大多数货cargo test期间排除它们。您可以使用ignore属性将耗时的测试排除在外,而不是将您想要运行的所有测试作为参数列出,如下所示:
文件名:src/lib.rs
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
在#[test]之后,我们将#[ignore]行添加到我们想要排除的测试中。现在,当我们运行我们的测试时,it_works会运行,但是expensive_test不会:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test expensive_test ... ignored
test it_works ... ok
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
expensive_test函数被列为ignored。如果我们只想运行被忽略的测试,我们可以使用cargo test - - ignored:
$ cargo test -- --ignored
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test expensive_test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
通过控制运行哪些测试,您可以确保cargo test结果会很快。当您有时间检查被ignored测试的结果并且有时间等待结果时,您可以运行cargo test - - ignored。如果你想运行所有的测试,不管它们是否被忽略,你可以运行cargo test -- --include-ignored。
本章重点
- 如何控制测试的用途
- 控制测试的方法
- 并行或连续测试的方法
- println!在测试中如何输出
- 单一测试和过滤运行多个测试
- 忽略测试的方法