Rust 测试:#[test] 属性与测试函数
Rust 语言内置了强大的测试功能,无需引入复杂的第三方库即可进行单元测试。掌握 #[test] 属性和相关的断言宏,是编写健壮 Rust 代码的基础。
1. 编写第一个测试函数
测试函数的本质是用于验证非测试代码是否按预期工作的函数。在 Rust 中,要让编译器识别一个函数为测试函数,必须对其添加 #[test] 属性。
创建一个新的二进制项目以进行练习:
cargo new adder
打开项目目录下的 src/main.rs 文件。在文件中,#[test] 属性通常位于测试函数的上方,紧接着是 fn 关键字定义的函数。测试函数内部通常使用断言宏来返回成功或失败。
输入以下代码到 src/main.rs 中:
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
在这段代码中,#[test] 标识了 it_works 是一个测试函数。assert_eq! 宏用于判断两个参数是否相等。
2. 运行测试
编写完测试代码后,需要通过 cargo test 命令来执行它们。该命令会编译项目并运行所有标记为 #[test] 的函数。
运行以下命令:
cargo test
终端会输出详细的测试信息。通常你会看到以下几部分内容:
- 编译信息:显示
Compiling adder和Finished test。 - **Running target/debug/deps/adder-...`:表示正在运行测试二进制文件。
- **Running unittests src/main.rs
:表示正在运行src/main.rs` 中的单元测试。 - Test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out:这是测试摘要。如果代码正确,会显示
1 passed。 - Doc-tests adder:这是文档测试的运行结果。
3. 使用断言宏验证结果
Rust 提供了多种断言宏来验证代码逻辑。最常用的是 assert!、assert_eq! 和 assert_ne!。
3.1 assert! 宏
assert! 宏接受一个布尔值参数。如果参数为 true,测试通过;如果为 false,测试会调用 panic! 导致失败。
编写一个检查逻辑的测试:
#[test]
fn test_boolean() {
let is_rust_fun = true;
assert!(is_rust_fun);
}
3.2 assert_eq! 和 assert_ne! 宏
这两个宏用于比较两个值是否相等或不相等。它们在失败时会自动打印出这两个值的实际内容,便于调试。
assert_eq! 判断相等,assert_ne! 判断不相等。
编写比较测试:
#[test]
fn test_addition() {
let sum = 5 + 5;
assert_eq!(sum, 10);
}
#[test]
fn test_not_equal() {
let value = 100;
assert_ne!(value, 99);
}
为了更清晰地对比这三个宏,请参考下表:
| 宏名 | 参数说明 | 用途 | 失败时的行为 |
|---|---|---|---|
assert! |
一个布尔表达式 | 验证条件是否为真 | 调用 panic! |
assert_eq! |
两个表达式 | 验证两个值是否相等 | 调用 panic! 并打印两个值 |
assert_ne! |
两个表达式 | 验证两个值是否不相等 | 调用 panic! 并打印两个值 |
4. 自定义失败信息
当断言失败时,Rust 允许添加自定义的错误信息,以便快速定位问题。这通过在断言宏参数的末尾添加格式化字符串来实现。
编写带有自定义信息的测试:
#[test]
fn test_with_message() {
let name = "Ferris";
assert!(
name == "Rustacean",
"名字应该是 'Rustacean' 但实际是 '{}'", name
);
}
运行 cargo test,你会看到类似以下的错误输出:
thread 'test_with_message' panicked at '名字应该是 'Rustacean' 但实际是 'Ferris'', src/main.rs:4:5
5. 测试 Panic 处理
除了验证代码返回正确的结果,还需要验证代码在遇到错误时是否按预期崩溃(Panic)。这需要使用 #[should_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 }
}
}
#[test]
#[should_panic]
fn test_greater_than_100() {
Guess::new(200);
}
在这个例子中,Guess::new(200) 应该会导致程序崩溃。因为添加了 #[should_panic] 属性,只要该函数触发了 Panic,测试就算通过。
5.1 精确匹配 Panic 信息
为了确保 Panic 是由特定的原因引起的,可以在 #[should_panic] 属性中添加 expected 参数。
修改上述测试代码,使其检查特定的错误信息:
#[test]
#[should_panic(expected = "Guess value must be between 1 and 100")]
fn test_greater_than_100_precise() {
Guess::new(200);
}
只有当 Panic 信息包含 "Guess value must be between 1 and 100" 这个子串时,测试才会通过。
6. 运行部分测试
随着项目变大,测试数量会随之增加,每次运行所有测试会浪费时间。cargo test 允许通过参数过滤需要运行的测试。
6.1 运行单个测试
运行指定名称的测试,只需将测试名称作为参数传递给 cargo test。
cargo test it_works
这将只运行名为 it_works 的测试函数。
6.2 运行部分匹配的测试
如果传递的名称匹配了多个测试,所有匹配的测试都会运行。
假设你有以下测试函数:
test_additiontest_subtraction
运行以下命令:
cargo test test_add
这将同时运行 test_addition,因为它的名称包含了 test_add。
7. 忽略某些测试
有时某些测试非常耗时(例如涉及数据库操作或复杂计算),你可能希望在大多数开发过程中跳过它们。这可以使用 #[ignore] 属性实现。
添加 #[ignore] 属性到耗时测试的上方:
#[test]
#[ignore]
fn expensive_test() {
// 耗时很长的代码
}
运行 cargo test,你会发现 expensive_test 被标记为 ignored,默认不会执行。
如果确实需要运行被忽略的测试,可以使用 --ignored 参数:
cargo test -- --ignored
暂无评论,快来抢沙发吧!