测试
用于验证非测试代码的功能是否与预期一致的函数
测试函数体的三个操作
1.准备数据/状态
2.运行被测试的代码
3.断言(Assert)结果
解剖测试函数
测试函数需要使用test属性(Attribute)进行标注.
Attribute就是一段Rust代码的元数据
在函数上加#[test],可以把函数变成测试函数
运行测试
使用cargo test命令运行所有测试,此时rust会构建一个test runner可执行文件,他会运行标注了test的函数,并报告其是否运行成功
当使用cargo创建library项目的时候,会生成一个test module,里面有一个test函数,你可以添加任意数量的test module
输入:
cargo new adder --lib //创建一个lib库项目
而后会得到一个library文件,内容如下:
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);
}
}
测试失败
测试函数中出现panic
每个测试运行在一个新的线程
当主线程看见某个测试挂掉了,那么这个测试标记为失败
Assert断言
使用assert!宏检查结果
assert!宏来自于标准库,用来确定某个状态是否为ture
true代表测试通过。
false代表测试失败,调用panic!。
example:
#[derive(Debug)]
pub struct Rctangle{
//创建结构体
lenth:u32,
width:u32,
}
impl Rctangle {
//添加结构体内的方法
pub fn can_hold(&self, other:&Rctangle) -> bool{
self.lenth > other.lenth && self.width > other.width
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test] //表明这是一个测试模块
fn large_can_hold_small() {
let large = Rctangle{
lenth: 8,
width: 9
};
let small = Rctangle{
lenth:6,
width:8,
};//assert!用于判断是否为true,所以can_hold用作测试的时候返回的是一个bool类型
assert!(large.can_hold(&small)) //调用了large对象的can_hold()方法,并传入small对象的引用
}
}
使用assert_eq!和asser_ne!测试相等性
都来自标准库
都是判断两个函数是否相等或者不等
实际上,它们使用的就是==或者!=运算符
断言失败则自动打印出两个参数的值
添加自定义错误消息
可以向assert!、assert_eq!、assert_ne!添加可选的自定义消息
—这些自定义消息和失败消息都会打印出来
—assert!:第一参数必填,自定义消息作为第二个参数。
—assert_eq!和assert_ne!: 前两个参数必填,自定义消息作为第三个参数。
—自定义消息参数会被传递给format!宏,可以使用{}占位符
example:
use std::fmt::format;
pub fn greeting(name: &str) -> String{
format!("hello {}!",name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greetings_contain_name(){
let result = greeting("carol");
assert!(
result.contains("Carol"),
"Greeting didn't contain name,value was '{}'",
result
);
}
}
用should_panic检查恐慌
验证错误处理的情况
测试除了验证代码的返回值是否正确,还需要验证代码是否预期的处理了发生的错误
可验证代码在特定情况下是否发生了panic
should_panic属性(attribute):
—函数panic:测试通过
—函数没有panic:测试失败
example:
pub struct Guess{
value:u32,
}
impl Guess {
pub fn new(value:u32)->Guess {
if value < 1 || value > 100 {
panic!("Guess value must between 1 and 100,got {}.",value)
}
Guess{
value}
}
}
#[cfg(test)]
mod tests{
use super::*;
#[test]
#[should_panic]
fn greater_than_100 () {
Guess::new(100)
}
}
让should_panic更加精确
pub struct Guess{
value:u32,
}
impl Guess {
pub fn new(value:u32)->Guess {
if value < 1 {
panic!("Guess value must greater than or equal to 1 ,got {}.",value)
}else if value > 100 {
panic!("Guess value must less than or equal than 100, got {}.",value)
}
Guess{
value}
}
}
#[cfg(test)]
mod tests{
use super::*;
#[test]
#[should_panic(expected="Guess value must less than or equal than 100")]
fn greater_than_100 () {
Guess::new(100);
}
}
在测试中使用Result<T,E>
无需panic,可使用Result<T,E>作为返回类型的编写测试
—返回OK:测试通过
—返回Err:测试失败
注意:不要在使用Result编写的测试上标注#[should_panic],因为这种测试不会发生恐慌
example:
#[cfg(test)]
mod tests {
#[test]
fn it_woks() ->Result<(),String>{
if 2+2 ==4 {
Ok(())
}else {
Err(String::from("two plus two does not equal four"))
}
}
}
控制测试如何运行
我们可以通过添加命令行参数来改变cargo test 的行为
默认行为:
—并行测试
—所有测试
—捕获(不显示)所有输出,使得读取和测试结果相关的输出更加容易
命令行参数:
—针对cargo test的参数,紧跟在cargo test之后
—针对测试可执行程序:放在–之后
并行/连续运行测试
并行
运行多个测试默认使用多个线程来并行运行
—运行快
确保测试之间不会相互依赖且不依赖于某个共享状态(环境、目录、环境变量等)
– test-threads参数
传递给二进制文件
不想以并行方式运行测试,或者想对线程进行细粒度控制
可以使用-- test-threads参数后面跟着线程的数量
显示函数输出
默认,如果测试通过,Rust的test库会捕获所有打印到标准输出的内容(println!输出的内容并不会在终端中看到)
如果想要在成功的测试中看到打印的内容则需要 --show-output
按名称运行测试
选择运行的测试:将测试的名字作为cargo test的参数
单个测试直接在cargo test后面添加函数名,如果运行多个则需要在cargo test后面添加测试名的一部分从而运行测试的部分模块
忽略测试
ignore属性()
#[cfg(test)]
mod tests {
#[test]
fn it_woks() ->Result<(),String>{
if 2+2 ==4 {
Ok(())
}else {
Err(String::from("two plus two does not equal four"))
}
}
#[test]
#[ignore] //这个标记下的测试在运行cargo test时不会进行测试运行
fn expensive_test() {
assert_eq!(5,1+1+1+1+1)
}
}
如果需要运行被忽略的测试
输入以下代码
cargo test -- --ignored
测试的分类
单元测试:
目的:将一小段代码隔离出来从而判断功能是否符合预期
小、专注
一次对一个模块进行隔离的测试
可测试private接口
注意:rust允许测试私有函数
example:
pub fn add_two(a:i32) ->i32{
internal_adder(a, 2)
}//最后的所有权归到了add_twoz()中
fn internal_adder(a:i32, b:i32) ->i32{
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(4,internal_adder(2, 2))
}
}
集成测试
目的:测试被测试库的多个部分能否正确的一起工作
在库的外部。和其他外部代码一样使用你的代码
只能使用public接口
可能在测试中使用到多个模块
创建集成测试的步骤:
1.首先需要创建一个tests目录,tests目录下每个测试文件都是一个单独的crate
——需要将被测试库导入
——这些文件不会共享行为
2.无需标记#[cfg(test)],tests目录被特殊对待
——只有cargo test,才会编译tests下的文件
运行指定的集成测试
1、运行一个特定的集成测试:cargo test 函数名
2、运行某个测试文件内的所有测试:cargo test --test 文件名
针对binary crate的集成测试
如果项目是binary crate,只含有src/main.rs没有src/lib.rs:
—不能在tests目录下创建集成测试
—即使有测试也无法把main.rs导入作用域
只有library crate才能暴露给其他crate用
binary crate意味着独立运行