【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
程序测试是一种非常有效方法来测试Bug的存在,但是对于显示它们的缺失是完全不够的。程序的正确性是指代码在多大程度上完成了我们想让它做的事情。Rust 在设计时高度关注程序的正确性,但正确性时复杂的,而且不容易证明。本章节将会讲解测试函数、单元测试、集成测试、性能测试等等。
主要教材参考 《The Rust Programming Language》
测试是 Rust 函数,用于验证非测试代码是否按照预期方式运行。测试函数的主体通常执行以下三个动作(3A)。
要将函数改为测试,需要 fn 之前添加 #[test]
,当你是使用 cargo test
命令运行测试时, Rust 会构建一个测试运行二进制文件,该文件运行带注释的函数,并报告每个测试函数是通过还是失败。
当我们使用 Cargo 创建一个库的项目时,系统都会自动为我们生成一个包含测试函数的测试模块。本模块为您提供了编写测试的模板,因此您不必每次开始新项目时查找确切的结构和语法。
在实际测试任何代码之前,我们将通过模板测试来探索测试如何工作的一些方面。
$ cargo new adder --lib
Created library `adder` project
$ cd adder
简单地来说,Rust中的测试时一个带有 test 属性(attribute)注释的函数。属性是关于 Rust 代码片段的元数据。
范例:测试函数
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
您可以添加任意数量的 test module 或 函数
使用 cargo test
命令运行所有测试函数,Rust 会构建一个 Test Runner 可执行文件,它会运行标注了 test 的函数,并报告其是否运行成功。
raojiamin@192 adder % cargo test
Compiling adder v0.1.0 (~/Desktop/code/rust_code/adder)
Finished test [unoptimized + debuginfo] target(s) in 1.98s
Running unittests src/lib.rs (target/debug/deps/adder-b51bc6501e028464)
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 adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
测试函数 panc 就表示失败,每个测试运行在一个新线程,当主线程看见某个线程挂掉了,那个测试标记为失败了。
范例代码:
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
#[test]
fn another(){
panic!("Make this test fail");
}
}
运行结果
Compiling adder v0.1.0 (~/Desktop/code/rust_code/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.84s
Running unittests src/lib.rs (target/debug/deps/adder-b51bc6501e028464)
running 2 tests
test tests::it_works ... ok
test tests::another ... FAILED
failures:
---- tests::another stdout ----
thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:17:9
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!
宏来自标准库,用来确定某个状态是否为 true,如果为 true,则测试通过,否则调用 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));
}
}
assert_eq!
和 assert_ne!
测试相等性都来自标准库,判断两个参数是否相等或不等,实际上,断言失败的时候,会自动打印出两个参数的值,使用 debug 格式打印参数,所以要求参数实现了 PartialEq 和 Debug Trait(所有的基本类型和标准库大部分类型都实现了)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
可以向 assert!、assert_eq!、 assert_ne!
可以添加可选的自定义信息。
自定义消息会被传递给 format! 宏,可以使用 {} 占位符。
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`",
result
);
}
测试出了验证代码的返回值是否正确,还需要验证代码是否如预期的处理发生了错误的情况。可验证代码在特定情况是否发生了 panic,should_panic 属性(attribute),函数如果发生了 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 的属性中有一个参数 expected,可以用于测试期望的发生恐慌时的错误消息是否符合预期。
// --snip--
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);
}
}
Result
无需 panic,可使用 Result
作为返回类型编写测试:
#[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"))
}
}
}
cargoo test
的行为:添加命令行参数cargoo test
的参数:紧跟 cargoo test
后cargo test --help
cargo test -- --help
运行多个测试,默认使用多线程并行运行,运行快。确保测试之间不会相互依赖,不依赖于某个共享状态(环境、工作目录、环境变量等等)。
例如 cargo test -- --test-threads=1
默认,如测试通过,Rust 的 test 库会补货所有打印到标准输出的内容。
例如:如果被测试代码中用到了 println!
,
println!
打印的内容; println!
打印的内容 和 失败信息;如果想在成功的测试中看到打印的内容:--show-output
选择运行的测试:将测试的名称(一个或多个)作为 cargo test 的参数
忽略某些测试,运行剩余测试
cargo test -- --ignored
Rust 对测试的分类:
在 tests 模块上的 #[cfg(test)] 标注 ,只有在运行 cargo test 才会编译和运行代码,运行 cargo build 则不会
集成测试在不同的目录,它不需要 #[cfg(test)] 标注
cfg:configuration(配置)
在Rust 中允许测试私有函数
在 Rust 里面,集成测试完全位于被测试库的外部,目的是为了测试被测试库的多个部分是否能正确的一起工作。
以上就是今天要讲的内容