测试(函数)
- 测试:
- 函数
- 验证非测试代码的功能是否和预期一样
- 测试函数体(通常)执行的3个操作:
- 准备数据/状态
- 运行被测试的代码
- 断言(assert)结果
- 解剖测试函数
- 测试函数需要使用test属性(attribute)进行标注
- attribute 就是一段Rust代码的元数据
- 在函数加#[test],可把函数变成测试函数
- 测试函数需要使用test属性(attribute)进行标注
- 运行测试
- 使用cargo test命令运行所有测试函数
- rust会构奸一个test runner可执行文件
- 它会运行标注了test的函数,并报告其运行是否成功
- rust会构奸一个test runner可执行文件
- 当使用cargo创建library项目的时候,会生成一个test module,里面有一个test函数
- 你可以添加任意数量的test Module 或 函数
#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } }
- 使用cargo test命令运行所有测试函数
- 测试失败
- 测试函数panic就表示失败
- 每个测试运行在一个新线程
- 当主线程看见某个测试线程挂掉了,那个测试标记为失败了
#[cfg(test)] mod tests { #[test] fn exploration() { assert_eq!(2 + 2, 4); } #[test] fn another() { panic!("Make this test fail"); } }
断言(assert)
- 使用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)); } } - 使用assert_eq! 和 assert_ne!测试相等性
- 都来自标准库
- 判断两个参数是否相等或不等
- 实际上,它们使用的就是 == 和 != 运算符
- 断言失败:自动打印出两个参数的值
- 使用debug格式打印参数的值
- 使用debug格式打印参数
- 要求参数实现了partialep和debug traits (所有的基本类型和标准库里大部分类型都实现了)
- 使用debug格式打印参数
- 使用debug格式打印参数的值
pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(4, add_two(2)); } }
- assert!宏, 来自标准库, 用来确定某个状态是否为true
- 自定义错误消息
- 可以向assert!、assert_eq!、assert_ne!添加可选的自定义消息
- 这些自定义消息和失败消息都会打印出来
- assert!:第1参数必填,自定义消息作为第2个参数。
- assert_eq!和 assert_ne!: 前两个参数必填,自定义消息作为第3个参数
- 自定义消息参数会被传递给format宏, 可以使用{}占位符
pub fn greeting(name: &str) -> String { String::from("Hello!") } #[cfg(test)] mod tests { use super::*; #[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!( result.contains("Carol"), "Greeting did not contain name, value was `{}`", result ); } }
- 可以向assert!、assert_eq!、assert_ne!添加可选的自定义消息
- 用 should_panic检查恐慌
- 验证错误处理的情况
- 测试除了验证代码的返回值是否正确,还需验证代码是否如预期的处理了发生错误的情况
- 可验证代码是特定情况下是否发生了panic
- should_panic属性(attribute):
- 函数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更精确
- 为should_panic属性添加一个可选的expected参数:
- 将检查失败消息中是否包含所指定的文字
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); } }
- 验证错误处理的情况
- 在测试中使用result<T,E>
- 无需panic,可使用Result<T,E>作为返回类型编写测试:
- 返回ok:测试通过
- 返回err: 测试失败
- 注意:不要在使用Result<T,E> 编写的测试上标注#[should_panic]
- 无需panic,可使用Result<T,E>作为返回类型编写测试:
控制测试如何运行
- 改变cargo test的行为:添加命令行参数
- 默认行为
- 并行运行
- 所有测试
- 捕获(不显示)所有输出, 使读取与测试结果相关的输出更容易
- 命令行参数:
- 针对cargo test的参数:紧跟cargo test后
- 针对测试可执行程序:放在-- 之后
- cargo test --help
- cargo test -- --htlp
- 并行运行测试
- 运动多个测试:默认使用多个线程并行运行
- 运行快
- 确保测试之间:
- 不会互相依赖
- 不依赖于某个共享状态(环境、工作目录、环境变量等等)
- 控制线程的数量
- 传递给二进制文件
- 不想以并行方式运行测试,或想对线程数进行细粒度控制
- 可以使用 --test-threads参数,后边跟着线程的数量
- 例如:cargo test -- -- test-threads=1
- 运动多个测试:默认使用多个线程并行运行
- 显式函数输出
- 默认,如测试通过,rust的test库会捕获所有打印到标准输出的内容
- 例如:如果被测试代码中用到了println!
- 如果测试通过:不会在终端看到println!打印的内容
- 如果测试失败:会看到Println!打印的内容和失败信息
- 如果想在成功的测试中看到打印的内容: -- show-output
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 的参数
- cargo test test_name test_name1 (参数只传一个)
- 运行单个测试:指定测试名
- 运行多个测试: 指定测试名的一部分(模块名也可以)
- 忽略测试,运行其它测试
- ignore(属性) atrribute
#[test] fn it_works() { assert_eq!(2 + 2, 4); } #[test] #[ignore] fn expensive_test() { // code that takes an hour to run }- 运行被忽略的测试:
- cargo test -- --ignored
- 测试的分类
- 单元测试
- 小、专注
- 一次对一个模块进行隔离的测试
- 可测试private接口
#[cfg(test)]标注#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } }- test 模块上的 #[cfg(test)] 标注:
- 只有运行cargo test才编译和运行代码
- 运行 cargo Build则不会
- 集成测试
- 在库外部,和其它外部代码一样使用你的代码
- 只能使用public接口
- 可能在每个测试中使用到多个模块
- 集成测试在不同的目录,它不需要 #[cfg(test)] 标注
- 单元测试
- cfg: configuratin(配置)
- 告诉rust下面的条目只有在指定的配置选项下才被包含
- 配置选项test:由Rust提供,用来编译和运行测试
- 只有cargo test 才会编译代码,包括模块中的helper函数和#[test]标注的函数
- 集成测试
- 在Rust里,集成测试完全位于被测试库的外部
- 目的:是测试被测试库的多个部分是否能正确的一起工作
- 集成测试的覆盖率很重要
- tests 目录
- 创建集成测试: tests目录
- test目录下的每个测试文件都是单独的一个crate
- cargo test
- 无需标注 #[cfg(test)],tests目录被特殊对待
- 只有cargo test,才会编译tests目录下的文件
- 运行指定的集成测试
- 运行一个特定的集成测试: cargo test函数名
- 运行某个测试文件内的所有测试: cargo test --test 文件名
- 针对 binary crate的集成测试
- 如果项目是binary crate,只含有src/main.rs 没有Src/lib.rs
-
不能在tests目录下创建集成测试
-
无法把main.rs的函数导入作用域
-
- 如果项目是binary crate,只含有src/main.rs 没有Src/lib.rs