7 个版本
0.3.1 | 2024年2月22日 |
---|---|
0.3.0 | 2024年2月21日 |
0.2.0 | 2022年10月31日 |
0.1.2 | 2022年4月10日 |
#52 in 测试
9,084 每月下载次数
用于 5 crates
21KB
244 行
arbtest
一个功能强大、API 简小、实现精简的属性测试库。
use arbtest::arbtest;
#[test]
fn all_numbers_are_even() {
arbtest(|u| {
let number: u32 = u.arbitrary()?;
assert!(number % 2 == 0);
Ok(())
});
}
lib.rs
:
一个功能强大、API 简小、实现精简的属性测试库。
use arbtest::arbtest;
#[test]
fn all_numbers_are_even() {
arbtest(|u| {
let number: u32 = u.arbitrary()?;
assert!(number % 2 == 0);
Ok(())
});
}
特性
- 单函数公共 API,
- 无宏,
- 自动最小化,
- 时间预算,
- 与 fuzzer 兼容的测试。
入口点是 arbtest
函数。它接受一个参数 —— 要测试的属性。属性是一个具有以下签名的函数
/// Panics if the property does not hold.
fn property(u: &mut arbitrary::Unstructured) -> arbitrary::Result<()>
u
参数是来自 arbitrary
crate 的有限随机数生成器。您可以使用 u
生成伪随机结构化数据
let ints: Vec<u32> = u.arbitrary()?;
let fruit: &str = u.choose(&["apple", "banana", "cherimoya"])?;
或者使用 arbitrary crate 的 derive 功能来自动生成任意类型
#[derive(arbitrary::Arbitrary)]
struct Color { r: u8, g: u8, b: u8 }
let random_color = u.arbitrary::<Color>()?;
属性函数应使用随机生成的数据来断言实现的某些有趣行为,这对于 任何 值都应成立。例如,将颜色转换为字符串然后解析它应得到相同的颜色
#[test]
fn parse_is_display_inverted() {
arbtest(|u| {
let c1: Color = u.arbitrary();
let c2: Color = c1.to_string().parse().unwrap();
assert_eq!(c1, c2);
Ok(())
})
}
在您提供了属性函数后,arbtest 会在循环中反复运行它,传递越来越多的 arbitrary::Unstructured
字节,直到属性崩溃。在失败时,会打印出一个种子。该种子可以用于确定性地重放失败。
thread 'all_numbers_are_even' panicked at src/lib.rs:116:9:
assertion failed: number % 2 == 0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
arbtest failed!
Seed: 0xa88e234400000020
通过返回的 ArbTest
对象上的构建器式 API,可以获得更多功能。
时间预算
arbtest(property).budget_ms(1_000);
arbtest(property).budget(Duration::from_secs(1));
budget
函数控制搜索循环运行多长时间,默认为 100 毫秒。此默认值可以通过 ARBTEST_BUDGET_MS
环境变量来覆盖。
大小约束
arbtest(property)
.size_min(1 << 4)
.size_max(1 << 16);
在内部,arbitrary::Unstructured
只是一个 &[u8]
—— 随机字节的切片。这个切片的长度决定了测试可以使用的随机性程度。较短的切片包含较少的熵,导致测试用例更简单。
size_min
和 size_max
参数控制这个切片的长度:在寻找失败时,arbtest
会从 size_min
逐步增加到 size_max
。
注意,在尝试最小化已知失败时,arbtest
将尝试比 size_min
更小的值。
重放和最小化
arbtest(property).seed(0x92);
arbtest(property).seed(0x92).minimize();
当指定了 seed
时,arbtest
使用种子生成一个固定的 Unstructured
并运行属性函数一次。这有助于在通过搜索找到失败种子后调试测试失败。
如果除了 seed
之外还设置了 minimize
,那么 arbtest
将尝试找到一个更小的种子,该种子仍然可以触发失败。你可以使用 budget
来控制最小化运行的时间。
代码何时运行
arbtest
函数不会立即运行代码。相反,它返回一个可以用来进一步调整行为的 ArbTest
构建器对象。实际的执行是从 ArbTest::drop
触发的。如果你不喜欢在 drop
中崩溃,你可以使用 ArbTest::run
方法显式触发执行
let builder = arbtest(property);
drop(builder); // This line actually runs the tests.
arbtest(property).run(); // Request the run explicitly.
错误
属性失败应通过 panic 报告,例如,使用 assert_eq!
宏。返回一个 Err(arbitrary::Error)
并不表示测试失败,它只是意味着没有足够的熵来完成测试。而不是返回一个 arbitrary::Error
,测试可能选择以一种非随机的方式继续。例如,在测试分布式系统时,你可能使用以下模板
while !u.is_empty() && network.has_messages_in_flight() {
network.drop_and_permute_messages(u);
network.deliver_next_message();
}
while network.has_messages_in_flight() {
network.deliver_next_message();
}
导入
推荐导入方式
[dev-dependencies]
arbtest = "0.3"
#[cfg(test)]
mod tests {
use arbtest::{arbtest, arbitrary};
fn my_property(u: &mut arbitrary::Unstructured) -> arbitrary::Result<()> { Ok(()) }
}
如果你想要 #[derive(Arbitrary)]
,你需要显式地为 arbitrary
crate 添加 Cargo.toml 依赖
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
[dev-dependencies]
arbtest = "0.3"
#[derive(arbitrary::Arbitrary)]
struct Color { r: u8, g: u8, b: u8 }
#[cfg(test)]
mod tests {
use arbtest::arbtest;
#[test]
fn display_parse_identity() {
arbtest(|u| {
let c1: Color = u.arbitrary()?;
let c2: Color = c1.to_string().parse();
assert_eq!(c1, c2);
Ok(())
});
}
}
请注意,arbitrary
是一个非开发依赖项。这不是严格必需的,但有助于允许下游的crate使用任意值的Color
来运行它们的测试。
设计
大部分的重活都是由arbitrary
crate来完成的。它的arbitrary::Unstructured
是一个出色的抽象,它既适用于覆盖率指导的模糊测试,也适用于自动最小化。也就是说,您可以直接将arbtest
属性插入到cargo fuzz
中,API完全兼容。
属性函数使用&mut Unstructured
作为参数,而不是T: Arbitrary
,允许用户强制生成他们想要的任何T
。这里的较小好处是实现简单——属性类型不是泛型的。更大的好处是,这个API更具表现力,因为它允许有交互式属性。例如,一个分布式系统的网络模拟不需要提前生成“故障计划”,它可以在测试运行期间使用u
来对要丢弃哪些现有的网络数据包做出动态决定!
“种子”是一个u64
,按照惯例用十六进制表示。种子的低32位指定了底层Unstructured
的长度。高32位是真正的随机种子,它被输入到一个简单的xor-shift中,以生成指定长度的Unstructured
。
如果您喜欢这个crate,您可能也会喜欢https://github.com/graydon/exhaustigen-rs。
依赖项
~92KB