14.4 属性测试
听
传统的单元测试依赖开发者手动编写输入-输出示例,虽然直观,但难以覆盖边界条件和异常路径。属性测试(Property-based Testing)通过自动生成大量随机输入,验证代码是否始终满足某些通用性质(即“属性”),从而发现隐藏的逻辑错误或未处理的边缘情况。
Rust 生态中有两个主流属性测试库:quickcheck 和 proptest。它们都基于“生成-验证”模式,但在灵活性、错误缩小(shrinking)能力和使用体验上有所不同。
quickcheck:简洁入门
quickcheck 是较早的属性测试库,API 简洁,适合快速上手:
[dev-dependencies]
quickcheck = "1.0"
基本用法:
use quickcheck::{QuickCheck, TestResult};
fn prop_reverse_reverse_is_original(xs: Vec<i32>) -> bool {
let mut rev = xs.clone();
rev.reverse();
rev.reverse();
rev == xs
}
#[test]
fn test_reverse_property() {
QuickCheck::new().quickcheck(prop_reverse_reverse_is_original as fn(Vec<i32>) -> bool);
}
你也可以返回 TestResult 以支持条件测试:
fn prop_division(a: i32, b: i32) -> TestResult {
if b == 0 {
return TestResult::discard(); // 跳过无效输入
}
TestResult::from_bool(a / b * b + a % b == a)
}
quickcheck 的局限在于:生成策略固定,自定义复杂类型较困难,且错误缩小能力有限。
proptest:更强大灵活
proptest 提供更精细的控制、更好的错误缩小和更丰富的生成策略,是当前推荐的选择:
[dev-dependencies]
proptest = "1.0"
示例:测试一个解析器是否满足“序列化后能正确反解析”:
use proptest::prelude::*;
#[derive(Debug, Clone)]
struct Point { x: i32, y: i32 }
fn serialize(p: &Point) -> String {
format!("{},{}", p.x, p.y)
}
fn deserialize(s: &str) -> Option<Point> {
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 2 { return None; }
Some(Point {
x: parts[0].parse().ok()?,
y: parts[1].parse().ok()?,
})
}
proptest! {
#[test]
fn parse_serialize_roundtrip(p in any::<Point>()) {
let s = serialize(&p);
let parsed = deserialize(&s).unwrap();
assert_eq!(p, parsed);
}
}
proptest 自动为 Point 推导生成策略(需实现 Arbitrary,可通过 #[derive(Arbitrary)] 自动生成)。若测试失败,它会自动尝试“缩小”输入(如将 [1000000, -999999] 缩小为 [0, 0]),快速定位最小复现案例。
自定义生成策略
对于特定范围或结构,可定制生成器:
proptest! {
#[test]
fn test_positive_addition(
a in 1..1000i32,
b in 1..1000i32
) {
assert!(a + b > 0);
}
}
或组合复杂结构:
let point_strategy = (0..100i32, 0..100i32).prop_map(|(x, y)| Point { x, y });
适用场景
- 验证代数性质(如结合律、交换律);
- 测试解析器/序列化器的往返一致性;
- 检查数据结构不变式(如 B 树平衡性);
- 发现整数溢出、空指针、越界等边界问题。
小结
属性测试不是替代单元测试,而是对其的有效补充。通过自动化生成海量测试用例,它能以极低成本覆盖人工难以想到的输入组合。proptest 凭借其强大的缩小机制和灵活的策略系统,已成为 Rust 社区的事实标准。在关键逻辑或复杂算法中引入属性测试,可显著提升代码的鲁棒性与可信度。建议从简单的往返测试或不变式验证开始,逐步将其融入测试套件。
#Rust 入门教程
分享于 5 天前
上一篇:14.3 mock 与依赖注入
下一篇:14.5 CI 集成与代码覆盖率