和 cargo run 会编译代码并运行生成的二进制程序一样,cargo test 会在测试模式下编译代码并运行生成的测试程序。此外,我们可以通过命令行参数来改变 cargo test 的默认行为。譬如,cargo test 默认情况下会并行运行所有测试,并捕获测试运行时产生的输出,避免显示出来,如 print,使测试结果更加易读。
我们可以同时将一部分命令行参数传递给 cargo test,另外一部分传递给测试程序。为了分隔这两种参数,我们在 cargo test 参数的后面追加分隔符 --,然后指定传递给测试程序的参数。可以通过运行 cargo test --help 查看 cargo test 的相关参数说明,运行 cargo test -- --help 查看分隔符 -- 之后可使用的相关参数说明。
控制测试的并行度
默认情况下 Rust 使用线程并行运行测试函数,也就是会尽快的运行完测试,以便可以更快的得到测试结果的反馈。但是,由于测试是并行运行的,我们需要确保测试不能有相互依赖,或者依赖任何共享的状态,包括共享的环境,比如当前工作目录或者环境变量(我理解,如果都是只读应该没关系)。
举例来说,假设每个测试都会在硬盘上创建一个文件 test-output.txt ,并写入一些数据,接着会读取文件中的数据并断言其是否包含特定的值,并且每个测试中的这个值都不相同。那么,因为所有测试是并行运行的,一个测试可能会在另一个测试读写文件过程中修改了文件内容,从而导致其测试失败,而失败的原因是测试的问题,它们在运行时互相干扰,并不是代码存在 bug。我们有两种方案可以解决这个问题,一是在每个测试中读写不同文件;另一个是一次只运行一个测试。
如果我们不希望测试并行运行,或者想要控制运行线程的数量,可以使用 --test-threads 参数,参考下面的例子:
$ cargo test -- --test-threads=1
复制代码
上例中将运行测试的线程数量设置为 1,这样就并行就不存在了,当然这样很可能需要更多的时间运行测试,但在有共享的状态时,测试之间就可以避免相互干扰。
显示输出
默认情况下,测试通过时 Rust 会截获打印到标准输出的所有内容。譬如,我们在代码中调用了 println! ,在测试通过的情况下我们将不会在终端看到 println! 的输出;如果测试失败了,在控制台才会打印出 println! 的输出。参考下面的例子:
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);
}
}
复制代码
运行上面例子中的测试,我们将会看到类似下面的结果:
$ cargo test
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running 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 'main' 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 ,同时还有相关失败信息的描述。
如果我们希望在测试通过时也能看到打印的内容,可以使用 --show-output 参数来禁用对输出的捕获:
$ cargo test -- --show-output
复制代码
再次运行测试,我们可以看到类似下面的结果:
$ cargo test -- --show-output
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running 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 'main' 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 传递测试名称作为参数来指定运行哪些测试。我们以下面的测试作为例子:
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));
}
}
复制代码
如果没有指定任何参数运行测试,我们将得到类似下面的结果:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running 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 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 表明有 2 个测试被过滤掉了,即 add_two_and_two 和 add_three_and_two,它们与指定的测试名称不匹配。
匹配多个测试
我们可以通过指定测试名称的一部分来匹配多个测试(即支持模糊匹配)。参考下面的例子:
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running 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 。另外需要注意的是,测试所在的模块也是测试名称的一部分 tests::add_three_and_two,因此我们也可以通过模块名来运行一个模块中所有的测试。
忽略某些测试
有些时候某些测试执行起来非常耗费时间或是其他原因我们需要运行绝大多数测试而跳过个别的测试,因此我们希望能在运行测试的时候对个别的测试进行排除。这时候我们可以使用 ignore 属性,参考下面的例子:
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
复制代码
运行测试,我们会看到类似下面的结果:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running 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,同时,摘要中也提示 1 ignored,也就是被忽略了,未执行。相反,如果我们希望运行标记了 ignore 的测试,可以使用 --ignored 参数(只对添加了 #[ignore] 忽略的测试有效):
$ cargo test -- --ignored
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running 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 。当我们需要运行被标记为 ignore 的比较耗时的测试并且也有时间等待执行结果时,还可以执行 cargo test -- --ignored。
评论