测试Rust代码
文章目录
- 一、assert!
- 二、使用 assert_eq! 和 assert_ne! 宏来测试相等
- 1.自定义报错消息
- 2.使用 should_panic 检查 panic
- 3.测试函数返回Result类型
- 三、cargo test 和cargo test --
- cargo test -- --test-threads=1
- cargo test -- --show-output打印失败运行的case的输出log
- 运行部分case
- 忽略test case
- 四、rust测试组织
- 单元测试
- 集成测试
- 在集成测试中引入公共的测试代码
- 集成测试不能直接测试二进制crate
- 参考
一、assert!
fn main() {}
#[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));}
}
编译及运行:
▶ cargo test
warning: function `main` is never used--> src/lib.rs:1:4|
1 | fn main() {}| ^^^^|= note: `#[warn(dead_code)]` on by defaultwarning: struct `Rectangle` is never constructed--> src/lib.rs:3:8|
3 | struct Rectangle {| ^^^^^^^^^warning: method `can_hold` is never used--> src/lib.rs:9:8|
8 | impl Rectangle {| -------------- method in this implementation
9 | fn can_hold(&self, other: &Rectangle) -> bool {| ^^^^^^^^warning: `adder` (lib) generated 3 warningsFinished `test` profile [unoptimized + debuginfo] target(s) in 0.00sRunning unittests src/lib.rs (target/debug/deps/adder-6facd3ec2ad675f4)running 2 tests
test tests::smaller_cannot_hold_larger ... ok
test tests::larger_can_hold_smaller ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sDoc-tests adderrunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.0
二、使用 assert_eq! 和 assert_ne! 宏来测试相等
fn main() {}
pub fn add_two(a: i32) -> i32 {a + 2
}#[cfg(test)]
mod tests {use super::*;#[test]fn it_adds_two() {// 传递给assert_eq!或assert_ne!的参数必须实现PartialEq和Debug的traitassert_eq!(4, add_two(2));}
}
1.自定义报错消息
fn main() {}
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);
}
2.使用 should_panic 检查 panic
fn main() {}
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);}
}
test cast总指定具体的#[should_panic]的内容,会与实际的发生panic!()的内容进行比较
fn main() {}
pub struct Guess {value: i32,
}// --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 = "Guess value must be less than or equal to 100")]fn greater_than_100() {Guess::new(200);}
}
3.测试函数返回Result类型
#![allow(unused)]
fn main() {}
#[cfg(test)]
mod tests {#[test]fn it_works() -> Result<(), String> {if 1 + 2 == 4 {Ok(())} else {Err(String::from("two plus two does not equal four"))}}
}
▶ cargo testCompiling adder v0.1.0 (/home/wangji/code/rust/2024_rust/rust_project/adder)Finished `test` profile [unoptimized + debuginfo] target(s) in 0.63sRunning unittests src/lib.rs (target/debug/deps/adder-6facd3ec2ad675f4)running 1 test
test tests::it_works ... FAILEDfailures:---- tests::it_works stdout ----
Error: "two plus two does not equal four"failures:tests::it_workstest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00serror: test failed, to rerun pass `--lib`
三、cargo test 和cargo test –
cargo test – --test-threads=1
cargo test – 是为生成的二进制测试程序设置一些额外参数
cargo test 是适用于本身的参数
▶ cargo test -- --test-threads=1
cargo test – --show-output打印失败运行的case的输出log
默认失败的case中,如果被测函数有一些打印的话,是不会有输出内容的
▶ cargo test -- --show-output
#![allow(unused)]
fn main() {}
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);}
}
运行部分case
#![allow(unused)]
fn main() {}
pub fn add_two(a: i32) -> i32 {a + 2
}#[cfg(test)]
mod tests {use super::*;#[test]fn add_two_and_two() {assert_eq!(4, add_two(2));}#[test]fn add_three_and_two() {assert_eq!(5, add_two(3));}#[test]fn one_hundred() {assert_eq!(102, add_two(100));}
}
仅运行一个test case
▶ cargo test one_hundredFinished `test` profile [unoptimized + debuginfo] target(s) in 0.00sRunning unittests src/lib.rs (target/debug/deps/adder-6facd3ec2ad675f4)running 1 test
test tests::one_hundred ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
运行add前缀的case
▶ cargo test addFinished `test` profile [unoptimized + debuginfo] target(s) in 0.00sRunning unittests src/lib.rs (target/debug/deps/adder-6facd3ec2ad675f4)running 2 tests
test tests::add_two_and_two ... ok
test tests::add_three_and_two ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
运行test模块中的case
▶ cargo test tests::
忽略test case
#![allow(unused)]
fn main() {}
mod tests {#[test]fn it_works() {assert_eq!(2 + 2, 4);}#[test]#[ignore]fn expensive_test() {// 需要运行一个小时的代码}
}
编译及运行
▶ cargo test tests:: -- --test-threads=10
就是要运行ignore的case
▶ cargo test -- --test-threads=10 --ignored
四、rust测试组织
单元测试
单元测试的代码和被测试的代码是在同一个文件中的
fn main() {}pub fn add_two(a: i32) -> i32 {internal_adder(a, 2)
}fn internal_adder(a: i32, b: i32) -> i32 {a + b
}// 测试模块一般呈称之为tests
#[cfg(test)]
mod tests {use super::*; //子模块访问父模块的函数// rust 可以测试私有函数// 单元测试代码可以与被测代码在一个文件中(通常在),也可以不在一个文件中#[test]fn internal() {assert_eq!(4, internal_adder(2, 2));}
}
集成测试
use adder; //来自于Cargo.toml中的name = "adder"
// cargo会将tests目录下的每个文件编译成一个单独的crate#[test]
fn it_adds_two() {assert_eq!(4, adder::add_two(2));
}
▶ cargo test
仅仅运行集成测试
▶ cargo test --test integration_test
warning: function `main` is never used--> src/lib.rs:1:4|
1 | fn main() {}| ^^^^|= note: `#[warn(dead_code)]` on by defaultwarning: `adder` (lib) generated 1 warningFinished `test` profile [unoptimized + debuginfo] target(s) in 0.94sRunning tests/integration_test.rs (target/debug/deps/integration_test-97da6b451065e35f)running 1 test
test it_adds_two ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
子目录中的文件不会被单独编译成一个crate,进行运行
在集成测试中引入公共的测试代码
use adder; //来自于Cargo.toml中的name = "adder"// cargo会将tests目录下的每个文件编译成一个单独的crate// 模块定义语法
// 内容要么来自common.rs文件,要么来自common目录下的文件
mod common;#[test]
fn it_adds_two() {common::setup();assert_eq!(4, adder::add_two(2));
}
集成测试不能直接测试二进制crate
二进制crate具有main.rs的项目,解决办法:需要一个轻量级的库crate去包装二进制的crate,通过库crate间接测试二进制crate
参考
- 第11章~测试Rust代码(上)
- 如何编写测试