基础标记说明
Rust 是具备内嵌单元测试模块的。在 Rust 中,可以通过在源代码文件的顶部使用 #[test]
属性来标记一个函数作为测试函数。通常,这些测试函数位于与它们测试的源代码相同的文件中,但位于 mod tests
模块中。这是一个常用做法。
另外还有一个标记属性。#[cfg(test)]
是一个条件编译属性,它允许你编写只在测试构建中编译的代码。这对于定义只在测试时需要的辅助函数、类型或模块特别有用,从而避免在生产代码中引入不必要的开销或依赖。
创建一个测试代码:
fn im_true() -> bool { true
}fn is_false() -> bool {false
}fn is_num_three() -> u32 {3
}fn is_string() -> String {String::from("hello")
}
接下来测试函数 is_false 的返回是否为布尔值:否,需要先建立一个测试模块,然后建立一个测试函数
#[cfg(test)] mod test {use crate::is_false;#[test]// check if is_false return falsefn check_bool_false() {let f = is_false();assert!(!f, "is not false");}#[test]// check if return truefn check_bool_true() {let f = is_false();assert!(f, "is not true");}}
上面的代码中建立了一个名字为 test 的测试模块,由于标记了#[cfg (test)]标签,所以在执行 cargo build 时是不会执行编译的,只有在执行 cargo test 时会编译执行。另外建立了两个单元测试内容:check_bool_false, check_bool_true,两个函数都用#[test]进行了标记。当执行 cargo test 时会看到两个测试项的结果。上述代码比较简单,应该是一个成功一个失败:
#cargo test
......
running 2 tests
test test::check_bool_false ... ok
test test::check_bool_true ... FAILEDfailures:---- test::check_bool_true stdout ----
thread 'test::check_bool_true' panicked at src/main.rs:37:9:
is not true
note: run with `RUST_BACKTRACE=1` environment variable to display a backtracefailures:test::check_bool_truetest result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00serror: test failed, to rerun pass `--bin unitestlearn`
成功的单元测试会描述为 ok,而失败的会使用:FAILED 标记进行提示。同时会有标准输出以及总结哪些测试用例失败的描述。
断言
上述代码中有个关键字 assert!,这个关键字是检查给定的布尔表达式是否为真。如果为假,则测试失败。类似的关键字还有很多
关键字 | 说明 |
---|---|
assert! | 检查给定的布尔表达式是否为真。如果为假,则测试失败。 |
assert_eq! | 检查两个表达式是否相等。如果不等,则测试失败 |
assert_ne! | 检查两个表达式是否不相等。如果相等,则测试失败 |
Assert_debug_snapshot! | 用于比较当前代码的调试输出是否与先前存储的快照匹配,这有助于在重构代码时确保其行为未改变 (依赖 insta ) |
以下是一些案例
#[test] ----- 布尔值检查
// check if return true
fn check_bool_true() {let f = im_true();assert!(f, "is not true");
}#[test] ---- 数字相等检查
fn check_equal_num() {assert_eq!(2,2);
}#[test] ----- 数字不等检查
fn check_ne_num() {assert_ne!(3,2);
}#[test] ----- 快照检查
fn check_snapshot() {let mut settings = insta::Settings::clone_current();settings.set_snapshot_path("../test/snapshot/");settings.bind(|| {let name = "helloworld";insta::assert_debug_snapshot!(name);});
}
快照检查需要多说一下,写快照检查时需要引入一个依赖,所以在 Cargo. Toml 中需要增加如下内容
[dependencies]
insta = "1.38.0"
在项目目录下创建一个 test/snapshot 目录,该目录将作为快照存储的目录,代码中采用 settings.set_snapshot_path 进行设置,settings. Bind 中设置测试流程,第一次执行时测试用例会失败,并自动在快照目录下生成一个快照文件,如上面的用例会生成一个:unitestlearn__test__check_snapshot.snap. New 的文件,生成后将该文件 new 的后缀去掉后保存就作为一个快照保存即可。
声明式宏
在 Rust 中可以通过 macro_rules!进行声明式宏的定义。能够减少重复代码的编写,简化代码,大大提高可读性和编码效率。
以下面为例:
macro_rules! snapshot_test {
($testname:ident, $txtcontent:literal) => {#[test]fn $testname() {let mut settings = insta::Settings::clone_current();settings.set_snapshot_path("../test/snapshot/");settings.bind(|| {let name = $txtcontent;insta::assert_debug_snapshot!(name);});}}
}snapshot_test!(a_check, "a_check");
snapshot_test!(b_check, "b_check");
snapshot_test!(c_check, "c_check");
类似这种及其相似的代码就可以使用宏定义的方式快速实现。Macro_rules 的格式为
macro_rules! [宏定义名称] {() => {}// 其中括号为入参,大括号为函数体
}
针对宏定义的参数类型有以下几种
类型 | 说明 |
---|---|
ident | 标识符,如变量名或函数名。 |
block | 代码块,包括 { ... } 内的所有内容。 |
stmt | 语句,如赋值或函数调用。 |
expr | 表达式,可以求值为某个值的代码片段。 |
pat | 模式,用于匹配值或解构数据。 |
ty | 类型表达式,如 int 或 Vec<T> 。 |
lifetime | 生命周期标注,如 'a 。 |
literal | 字面量,如字符串、整数或浮点数字面量。 |
path | 路径,用于引用模块、类型或值。 |
meta | 元项,如属性(attributes)。 |
tt | 令牌树(token tree),可以包含任何有效的 Rust 令牌。 |
item | 项,可以是一个函数、结构体定义等。 |
vis | 可见性修饰符,如 pub 或 priv 。 |