Rust参数化测试宏parameterized-macro的使用,简化多参数场景下的单元测试编写
Rust参数化测试宏parameterized-macro的使用,简化多参数场景下的单元测试编写
测试用例生成的高级概述
一个示例
使用这个宏,我们可以从给定的参数化测试定义生成以下代码:
参数化测试定义:
#[parameterized(a = {1, 2}, b = { "wanderlust", "wanderer" })]
fn my_test(a: i32, b: &str) {
assert!(a > 0 && b.starts_with("w"))
}
生成的测试用例:
#[cfg(test)]
mod my_test {
#[test]
fn case_0() {
let a: i32 = 1;
let b: &str = "wanderlust";
assert!(a > 0 && b.starts_with("w"))
}
#[test]
fn case_1() {
let a: i32 = 2;
let b: &str = "wanderer";
assert!(a > 0 && b.starts_with("w"))
}
}
注意事项:
- 生成的函数名称与原始模块名称相同
- 参数不限于基本类型,可以是任何表达式
- 属性中指定的输入参数应该评估为与函数签名中同名参数相同的类型
- 从工作区crate执行的测试应该单独运行
完整示例代码
下面是一个更完整的参数化测试示例,展示了不同类型的使用场景:
// 首先添加依赖到Cargo.toml
// parameterized-macro = "2.0.0"
use parameterized::parameterized;
// 示例1: 基本类型测试
#[parameterized(
a = {1, 2, 3},
b = {"hello", "world", "rust"}
)]
fn test_string_contains(a: i32, b: &str) {
// 测试a是正数且b字符串长度等于a
assert!(a > 0 && b.len() == a as usize);
}
// 示例2: 集合类型测试
#[parameterized(
input = {
vec![1, 2, 3],
vec![4, 5, 6],
vec![7, 8, 9]
},
expected = {6, 15, 24}
)]
fn test_sum(input: Vec<i32>, expected: i32) {
// 测试向量求和
assert_eq!(input.iter().sum::<i32>(), expected);
}
// 示例3: 自定义结构体测试
#[parameterized(
user = {
User { name: "Alice".to_string(), age: 30 },
User { name: "Bob".to_string(), age: 25 },
User { name: "Charlie".to_string(), age: 35 }
},
expected = {true, false, true}
)]
fn test_user_age(user: User, expected: bool) {
// 测试用户年龄是否大于等于30
assert_eq!(user.age >= 30, expected);
}
// 自定义结构体
#[derive(Debug)]
struct User {
name: String,
age: u8,
}
这个示例展示了:
- 基本类型(i32, &str)的参数化测试
- 集合类型(Vec<i32>)作为参数
- 自定义结构体作为参数
要运行这些测试,可以使用:
cargo test
或者单独运行某个测试:
cargo test test_string_contains -- --exact
1 回复
Rust参数化测试宏parameterized-macro的使用
parameterized-macro
是一个Rust宏,用于简化多参数场景下的单元测试编写。它允许你为同一个测试函数提供多组输入参数,而不需要为每组参数重复编写测试函数。
安装
在Cargo.toml
中添加依赖:
[dev-dependencies]
parameterized-macro = "0.3"
基本使用方法
#[cfg(test)]
mod tests {
use parameterized_macro::parameterized;
#[parameterized(input = {
1, 2, 3
})]
fn test_is_positive(input: i32) {
assert!(input > 0);
}
}
多参数测试
#[parameterized(input = {
(1, 1), (2, 4), (3, 9)
})]
fn test_square((input, expected): (i32, i32)) {
assert_eq!(input * input, expected);
}
命名测试用例
#[parameterized(input = {
case1 = {1, 1},
case2 = {2, 4},
case3 = {3, 9}
})]
fn test_named_square((input, expected): (i32, i32)) {
assert_eq!(input * input, expected);
}
更复杂的示例
#[parameterized(user = {
User { name: "Alice".to_string(), age: 30 },
User { name: "Bob".to_string(), age: 25 },
User { name: "Charlie".to_string(), age: 35 }
})]
fn test_user_age(user: User) {
assert!(user.age >= 18);
}
与标准测试宏结合使用
#[parameterized(input = {
"hello", "world", "rust"
})]
#[test]
fn test_string_length(input: &str) {
assert!(input.len() > 0);
}
完整示例demo
// 定义用户结构体
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
#[cfg(test)]
mod tests {
use super::*;
use parameterized_macro::parameterized;
// 基本测试示例 - 测试正数
#[parameterized(input = {
1, 2, 3
})]
fn test_is_positive(input: i32) {
assert!(input > 0);
}
// 多参数测试 - 平方计算
#[parameterized(input = {
(1, 1),
(2, 4),
(3, 9)
})]
fn test_square((input, expected): (i32, i32)) {
assert_eq!(input * input, expected);
}
// 命名测试用例 - 平方计算(带命名)
#[parameterized(input = {
case1 = {1, 1},
case2 = {2, 4},
case3 = {3, 9}
})]
fn test_named_square((input, expected): (i32, i32)) {
assert_eq!(input * input, expected);
}
// 复杂类型测试 - 用户年龄验证
#[parameterized(user = {
User { name: "Alice".to_string(), age: 30 },
User { name: "Bob".to_string(), age: 25 },
User { name: "Charlie".to_string(), age: 35 }
})]
fn test_user_age(user: User) {
assert!(user.age >= 18);
}
// 与标准测试宏结合 - 字符串长度测试
#[parameterized(input = {
"hello",
"world",
"rust"
})]
#[test]
fn test_string_length(input: &str) {
assert!(input.len() > 0);
}
}
优点
- 减少重复代码 - 不需要为每组参数编写单独的测试函数
- 提高可读性 - 所有测试用例集中在一个地方
- 更好的错误报告 - 每个测试用例都有独立的报告
注意事项
- 确保测试函数名和参数名清晰表达测试意图
- 避免在单个测试函数中使用过多测试用例
- 复杂的测试逻辑可能更适合单独的函数
这个宏特别适合测试边界条件、等价类划分等需要多组输入参数的测试场景。