3个版本
0.1.6 | 2021年3月28日 |
---|---|
0.1.5 | 2021年3月26日 |
0.1.4 | 2021年3月25日 |
在过程宏中排名第535位
43KB
287 行
Tlayuda - 测试对象构建器
为结构体添加生成结构体实例的静态方法的过程宏,配置简单。目标是提供一个简单的方法来生成动态测试数据,同时只需要设置与给定单元测试相关的对象字段。
用法示例
#[derive(Tlayuda)]
pub struct Student {
id: u64,
first_name: String,
last_name: String,
telephone: String,
date_of_birth: String,
final_grade: u32,
}
fn group_students_by_grade(mut students: Vec<Student>) -> StudentsPartitionedByGrade {
let result = StudentsPartitionedByGrade {
a_students: Vec::new(),
b_students: Vec::new(),
c_students: Vec::new(),
d_students: Vec::new(),
f_students: Vec::new(),
};
students.drain(..).fold(result, |mut acc, student| {
match student.final_grade {
90..=100 => acc.a_students.push(student),
80..=89 => acc.b_students.push(student),
70..=79 => acc.c_students.push(student),
60..=69 => acc.d_students.push(student),
0..=50 => acc.f_students.push(student),
_ => (),
}
acc
})
}
给定上述代码,覆盖 group_students_by_grade
所有方面的单元测试需要手动构建多个Student实例,并设置函数测试中未使用的字段。Tlayuda帮助填充假数据,并提供仅设置您关心的字段的函数。
#[test]
fn test_group_students_by_grade() {
// sets up a vec of students with 10 students per number grade
let students = Student::tlayuda() // create tlayuda test builder
.set_final_grade(|index| (index % 101) as u32) // returns a 0-100 grade based on index
.build_vec(200); // creates a vec of 200 students
// call function we're testing
let result = group_students_by_grade(students);
// verifies expected # of students per group
assert_eq!(20, result.a_students.len());
assert_eq!(20, result.b_students.len());
assert_eq!(20, result.c_students.len());
assert_eq!(20, result.d_students.len());
assert_eq!(102, result.f_students.len());
// verifies every group has the correct grade range
result.a_students.iter().for_each(|s| assert!(s.final_grade >= 90 && s.final_grade <= 100));
result.b_students.iter().for_each(|s| assert!(s.final_grade >= 80 && s.final_grade < 90));
result.c_students.iter().for_each(|s| assert!(s.final_grade >= 70 && s.final_grade < 80));
result.d_students.iter().for_each(|s| assert!(s.final_grade >= 60 && s.final_grade < 70));
result.f_students.iter().for_each(|s| assert!(s.final_grade <= 50));
}
如何使用
在结构体上方添加Tlayuda
过程宏。
use tlayuda::*;
#[derive(Tlayuda)]
pub struct Person {
id: u32,
first_name: String,
last_name: String,
is_active: bool
}
这将根据源结构的名称生成一个构建器结构体Tlayuda{}Builder
。在源结构体tlayuda
上添加一个静态方法,该方法返回构建器结构体的一个实例。在构建器对象上调用build()
将根据每个字段的类型和其索引(构建器存储的递增ID)使用“动态默认值”生成源结构体实例。
let mut builder = Person::tlayuda();
let person = builder.build();
assert_eq!(0, person.id);
assert_eq!("first_name0", person.first_name);
assert_eq!("last_name0", person.last_name);
assert_eq!(false, person.is_active);
// builder increments the internal index
// with each call to build
let person = builder.build();
assert_eq!(1, person.id);
assert_eq!("first_name1", person.first_name);
assert_eq!("last_name1", person.last_name);
assert_eq!(false, person.is_active);
构建器还将为结构体中的每个字段提供一个以set_
为前缀的方法,该方法接受一个闭包,其形式为FnMut(usize) -> Type
。usize参数是构建对象的“索引”。闭包的返回类型应与字段的类型相匹配。这可以用来设置自定义的字段值,而无需设置与当前测试无关的整个结构体。
let mut builder = Person::tlayuda()
.set_first_name(|i| {
if i == 1 {
"Michael".to_string()
} else {
format!("first_name{}", i)
}
});
let person = builder.build();
assert_eq!("first_name0", person.first_name);
let person = builder.build();
assert_eq!("Michael", person.first_name);
构建器还可以通过调用build_vec
生成结构体的Vec::<_>
。这内部使用构建器的当前设置来生成数据,并在每次构建后递增索引。
// builds 1000 Person objects and verifies
// each object has a unique first_name value
Person::tlayuda()
.set_first_name(|i| i.to_string())
.build_vec(1000)
.iter()
.enumerate()
.for_each(|(i, x)| {
assert_eq!(i.to_string(), x.first_name);
});
您还可以通过调用with_index(index: usize)
来更改构建器的起始索引。此外,对构建器对象的每次调用(不包括对build
或build_vec
的调用)都会返回构建器,以允许链式调用。
Person::tlayuda()
.set_first_name(|i| match (i % 3, i % 5) {
(0, 0) => "FizzBuzz".into(),
(0, _) => "Fizz".into(),
(_, 0) => "Buzz".into(),
_ => i.to_string(),
})
.with_index(1)
.build_vec(100)
Tlayuda还会自动尝试递归构建字段,如果它们不是已知支持的类型之一。也就是说,如果struct A
有一个字段是struct B
(它也使用了Tlayuda推导宏),则struct A
构建器将自动调用struct B
的构建器。注意:如果内部结构体有不受支持的字段或未使用Tlayuda宏,这会导致编译错误。
#[derive(Tlayuda)]
pub struct StructA {
pub some_field: u32,
pub another_struct: StructB,
}
#[derive(Tlayuda)]
pub struct StructB {
pub field_on_b: String,
}
let some_A = StructA::tlayuda().build();
assert_eq!("field_on_b0", some_A.another_struct.field_on_struct_b);
支持的类型
目前Tlayuda支持仅由以下类型组成的结构体
- 数字原始类型(i8-i128,u8-u128,f32,f64,isize,usize)
- 布尔值
- 字符
- 字符串、OsString
- Vecs
- 具有数字原始类型的数组
- 仅由上述类型组成(且使用Tlayuda宏)的结构体
具有完整路径的类型将忽略其路径,并按最后一段的处理方式表现。例如,“std::ffi::OsString”将视为“OsString”。
虽然目标是支持尽可能多的类型,但当前可能会遇到不支持的类型。在不受支持的字段上方添加一个 tlayuda_ignore
属性将标记该字段为跳过。相反,将修改 .tlayuda()
函数以接受该类型的参数,该参数将在构建过程中克隆以填充该字段。
#[derive(Tlayuda)]
pub strut StructA {
pub some_field: u32,
pub some_other_field: bool,
#[tlayuda_ignore] // add attribute above unsupported types
pub some_unsupported_type: Vec::<u32>,
}
/* inside a test */
let some_vec: Vec::<u32> = vec![1, 2, 3]; // construct a value for the unsupported type
let mut builder = StructA::tlayuda(some_vec); // ignored field now required as a parameter instead of being handled by tlayuda
let some_1 = builder.build();
assert_eq!(100, some_1.some_unsupported_type[0]); // value gets populated with value passed into tlayuda()
let some_2 = builder.build();
assert_eq!(100, some_2.some_unsupported_type[0]); // value is cloned across builds
在测试之外运行
默认情况下,Tlayuda 仅在执行测试时工作;宏使用 cfg[(test)] 属性输出代码,因此它仅影响测试。虽然对象的结构应在 Tlayuda 的各个版本之间保持一致,但生成的代码的意图和设计是针对测试目的。如果您需要在测试之外使用 Tlayuda,可以通过启用 "allow_outside_tests" 功能来实现。
当前待办事项列表
- 添加 vec 作为支持类型
- 修复失败的 Doc 测试
- 向 tlayuda_ignore 属性添加 "order" 参数以自定义
tlayuda()
参数顺序 - 为数组添加更多类型支持(包括嵌套数组)
- 支持 HashMaps
- 支持由当前支持的类型组成的元组
- 添加匹配访问修饰符(public/private)以避免泄露私有类型
依赖项
~1.5MB
~34K SLoC