十九、测试
Published on
测试是检查其它代码是否正常工作的Rust函数。典型的测试函数执行以下三个动作:
- 设置需要的数据和状态
- 运行测试代码
- 检查结果是否符合预期
如何编写测试函数
使用cargo new建立一个库项目时,会自动生成一个测试模块。测试函数前需要添加#[test]属性,之后使用cargo test命令会自动运行测试。
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
使用assert!宏检查结果
assert!用于保证条件为true,为true则继续执行程序,为false则调用panic!。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[cfg(test)]
mod tests {
use super::*;
// 此测试会成功
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
// 此测试会失败
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
使用assert_eq!与assert_ne!宏检查相等
// 函数无bug,测试通过
pub fn add_two(a: i32) -> i32 {
a + 2
}
// 函数有bug,测试不通过
pub fn add_two(a: i32) -> i32 {
a + 3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
assert_eq!(4, add_two(2));
}
}
添加自定义失败信息
使用assert!等宏时可以添加额外参数用于打印自定义失败信息,额外参数被传入format!宏进行格式化。
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`",
result
);
使用should_panic检查panic
在测试函数前添加#[should_panic]属性,此时如果此函数panic则测试通过,不panic则测试不通过。
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
使用should_panic的expected参数指定panic中必须包含指定字符串:
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
在测试中使用Result
测试函数可以返回Result,如果有错误则返回Err。此时可以使用?操作符。
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
测试返回Result时不能使用#[should_panic]属性,如果需要判断一个操作是否返回Err,不要使用?操作符,使用assert!(value.is_err())判断。
控制测试如何运行
并行或顺序运行测试
当运行测试时,默认使用多线程,此时必须保证测试之间不能相互信赖。如果要顺序执行,可以使用–test-threads参数:
$ cargo test -- --test-threads=1
显示函数输出
默认情况下,被测试代码的输出不会显示,如下面的函数中的println!宏的输出:
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {}", a);
10
}
如果需要显示,使用– –show-output参数:
$ cargo test -- --show-output
使用名称指定运行部分测试
运行单个测试:
cargo test one_hundred
运行所有包含add的测试
cargo test add
忽略一些测试
在测试函数前添加#[ignore]属性,则默认情况下此测试被忽略。
#[test]
#[ignore]
fn expensive_test() {
}
如果要只运行这些测试则使用:
cargo test -- --ignored
如果要运行全部测试则使用:
cargo test -- --include-ignored
单元测试
单元测试的目的是独立地测试每个单元的功能,代码放在每个源文件内部,惯例是添加一个名为tests的模块并添加#[cfg(test)]属性。
tests模块与#[cfg(test)]属性
由于单元测试在代码文件中,一般使用#[cfg(test)]属性表示不要在编译时包含测试代码。
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
测试私有函数
由于Rust的隐私规则,在子模块中可以测试父模块的私有函数。
fn internal_adder(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}
集成测试
集成测试在库的外部,与其它代码使用同样的方式调用库,因此只能调用公有接口。集成测试用于测试库中不同部分之间在一起可以正常工作。
tests目录
在src目录同级下建立tests目录,Cargo会在该目录下查找集成测试文件。
adder
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
└── integration_test.rs
tests目录中的集成测试文件需要使用use导入需要的库,并且不需要使用#[cfg(test)]属性。
use adder;
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
cargo test命令可以使用–test参数运行指定集成测试:
cargo test --test integration_test
集成测试中的子模块
与src目录不同,tests目录中的每个文件被编译为独立的箱,因此更加独立。假设tests/common.rs文件中有一个公用函数:
pub fn setup() {
}
可以在tests/integration_test.rs中使用该函数:
use adder;
// 声明common模块
mod common;
#[test]
fn it_adds_two() {
// 调用common模块的代码
common::setup();
assert_eq!(4, adder::add_two(2));
}
tests中每个文件都会进行测试并输出log,如果不想测试,可以将文件放入文件夹中,如tests/common/mod.rs。
对二进制箱进行集成测试
tests目录中的代码无法对二进制箱进行测试,因为无法使用use进行导入。因此Rust提供一个直接调用src/lib.rs文件的src/main.rs文件,src/lib.rs文件可以进行集成测试而src/main.rs文件中的少量代码无需测试。
文档测试
Rust中使用///添加文档注释,文档注释为Markdown格式,其中可以添加代码块,这些代码块被自动编译为文档测试。
/// First line is a short summary describing function.
///
/// The next lines present detailed documentation. Code blocks start with
/// triple backquotes and have implicit `fn main()` inside
/// and `extern crate <cratename>`. Assume we're testing `doccomments` crate:
///
/// ```
/// let result = doccomments::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
开发信赖
有时只有在测试时需要添加一些信赖,这些信赖添加至Cargo.toml的[dev-dependencies]节中。
[dev-dependencies]
pretty_assertions = "1"