Rust中的测试函数是用来验证非测试代码是否按照期望方式运行的。一般执行以下三种操作:
- 设置任何所需的数据或状态
- 运行所需要测试的代码
- 断言其结果是我们所期望的
测试函数剖析🔗
新建一个库项目,它的src/lib.rs内容如下:
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
我们关注it_works函数,fn行之前的#[test]表明这是一个测试函数。这里使用assert_eq!宏来断言result的值等于4。
使用cargo test命令来运行测试,结果如下:
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests testdd
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
可以看见我们通过了测试。
下面我们添加一个测试失败的例子,当测试函数出现panic时测试就失败了。
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exploration() {
let result = add(2, 2);
assert_eq!(result, 4);
}
#[test]
fn another() {
panic!("Make this test fail");
}
}
再次运行cargo test,结果如下:
running 2 tests
test tests::it_works ... ok
test tests::another ... FAILED
failures:
---- tests::another stdout ----
thread 'tests::another' panicked at src/lib.rs:17:9:
Make this test fail
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::another
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`
这里可以看见一个测试通过,一个测试失败。
使用宏来检查结果🔗
assert!由标准库提供,需要向assert!提供一个布尔值的参数。如果时间为true,就什么都不会做,如果是false,就会调用panic!宏。
assert_eq!和assert_ne!宏会比较两个值是否相等,两个宏都有两个参数,如果两个参数相等时,assert_eq!什么都不会做,assert_ne!会调用panic!宏,如果两个参数不相等时,assert_eq!会调用panic!宏,assert_ne!什么都不会做。
由于assert_eq!和assert_ne!宏底层分别使用了==和!=操作符,所以被比较的值必须实现了PartialEq和Debug这两个trait。
可以向assert!、assert_eq!和assert_ne!提供一个可选的失败信息参数,可以在测试失败时将自定义失败信息打印出来。在这三个函数必要的参数后面添加的参数都会传递给format!宏。
使用should_panic检查panic🔗
对函数增加另一个属性should_panic使这个函数在panic时通过,在没有panic时失败:
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);
}
}
#[should_panic]属性位于#[test]属性的下面之后。
然而,should_panic测试结果也可能含糊不清,在一些不是我们期望的原因而导致的panic时,测试也会通过。为了让测试更清晰,我们可以增加expected参数:
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);
}
}
这个测试会通过,因为panic信息包含了less than or equal to 100。
将*Result<T,E>*用于测试🔗
我们可以用Result<T,E>编写测试,这样在失败时可以返回Err而不是panic:
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() -> Result<(), String> {
let result = add(2, 2);
if result == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
这个函数体中没有调用assert_eq!宏,而是直接返回一个Result<T,E>。如果测试通过,就返回Ok(()),如果测试失败,就返回Err。
不能对Result<T,E>使用#[should_panic]注解,应该使用assert!宏,像这样:assert!(value.is_err())。