11.4 用should_panic检查恐慌
11.4.1. 验证错误处理的情况
除了验证代码是否返回正确的值,测试还需要验证代码是否如预期处理了错误情况。例如,可以编写一个测试来验证代码是否在特定条件下发生恐慌。
这类测试需要额外的should_panic属性。用它标记的函数,如果函数内发生了恐慌,测试就通过;否则就失败。
看个例子:
#![allow(unused)]
fn main() {
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
}
- 结构体
Guess有一个类型为i32的字段value。它提供关联函数new用于创建Guess实例,但前提是传给new的参数在1到100之间;否则就会恐慌。 greater_than_100测试函数给new传入大于100的值。这时应该发生恐慌,所以给这个测试函数加了should_panic属性,也就是写#[should_panic]。
测试结果:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
Running unittests src/lib.rs (target/debug/deps/guessing_game-bdc6b9a45c563cc0)
running 1 test
test tests::greater_than_100 - should panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests guessing_game
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
现在故意引入一个bug,把new里的value > 100检查去掉:
#![allow(unused)]
fn main() {
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
}
这时测试函数中的Guess::new(200);不会恐慌。但因为函数加了should_panic标记,本应恐慌却没有恐慌的测试就会失败:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.09s
Running unittests src/lib.rs (target/debug/deps/guessing_game-bdc6b9a45c563cc0)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
note: test did not panic as expected at src/lib.rs:21:8
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
11.4.2. 让should_panic更精确
有时使用should_panic的测试会有点含糊,因为它们只能说明代码是否发生了恐慌,即使这个恐慌并不是程序员预期的那个。
为了让测试更精确,可以为should_panic添加一个可选的expected参数。这样程序就会检查失败消息中是否包含指定的文字。
看个例子:
#![allow(unused)]
fn main() {
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {value}."
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {value}."
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
}
- 上面的结构体稍作了修改:
new里value < 1和value > 100的情况现在使用两条不同的恐慌信息。 - 给
should_panic添加了expected参数,=后面的文字就是期待的错误信息。只有函数发生了恐慌,并且恐慌信息包含期待的文字,测试才通过;否则就失败。
这个程序肯定能通过。
用同样的套路手动引入错误。例如,把new里小于1和大于100时的恐慌信息交换一下:
#![allow(unused)]
fn main() {
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be less than or equal to 100, got {value}."
);
} else if value > 100 {
panic!(
"Guess value must be greater than or equal to 1, got {value}."
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
}
测试结果:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s
Running unittests src/lib.rs (target/debug/deps/guessing_game-bdc6b9a45c563cc0)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
thread 'tests::greater_than_100' (457156) panicked at src/lib.rs:12:13:
Guess value must be greater than or equal to 1, got 200.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: "Guess value must be greater than or equal to 1, got 200."
expected substring: "less than or equal to 100"
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
失败信息表明测试确实发生了恐慌,但恐慌消息没有包含期待的字符串less than or equal to 100。在这种情况下,我们实际收到的恐慌信息是Guess value must be greater than or equal to 1, got 200.。根据这些信息就足以修复这个bug。