8 个版本
0.4.0 | 2024年7月3日 |
---|---|
0.3.1 | 2023年7月16日 |
0.3.0 | 2023年1月14日 |
0.2.1 | 2022年9月12日 |
0.1.0 | 2020年11月9日 |
#45 in 测试
76,159 每月下载量
在 74 个Crates中(直接使用68个) 使用
105KB
2K SLoC
test-strategy
此crate提供两个过程宏,分别为 #[derive(Arbitrary)]
和 #[proptest]
。
这些宏都是以下 proptest 官方宏的替代品。
test-strategy | proptest | proptest-derive |
---|---|---|
#[derive(Arbitrary)] |
#[derive(Arbitrary)] |
|
#[proptest] |
proptest! { } |
此crate提供的宏相对于 proptest 的官方宏有以下优势。
- 支持高阶策略。(
#[derive(Arbitrary)]
和#[proptest]
) - 代码格式化不会被禁用。(
#[proptest]
)
然而,此crate宏的语法与官方宏的语法不兼容。
安装
将此添加到您的 Cargo.toml 中
[dependencies]
test-strategy = "0.4.0"
proptest = "1.5.0"
示例
您可以使用 #[derive(Arbitrary)]
自动实现 proptest 的 Arbitrary
特性。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInputStruct {
x: u32,
#[strategy(1..10u32)]
y: u32,
#[strategy(0..#y)]
z: u32,
}
#[derive(Arbitrary, Debug)]
enum TestInputEnum {
A,
B,
#[weight(3)]
C,
X(u32),
Y(#[strategy(0..10u32)] u32),
}
您可以通过在函数中添加 #[proptest]
来定义属性测试。
use test_strategy::proptest;
#[proptest]
fn my_test(_x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) {
assert!(1 <= y && y < 10);
assert!(z <= y);
}
属性
属性可以写在以下位置。
属性 | 函数 | 结构体 | 枚举 | 变体 | 字段 | 函数参数 |
---|---|---|---|---|---|---|
#[策略] |
✔ | ✔ | ||||
#[任意] |
✔ | ✔ | ||||
#[权重] |
✔ | |||||
#[映射] |
✔ | ✔ | ||||
#[过滤] |
✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
#[by_ref] |
✔ | ✔ | ||||
#[任意(参数=T)] |
✔ | ✔ | ||||
#[任意(界限(...))] |
✔ | ✔ | ✔ | ✔ | ||
#[任意(转储)] |
✔ | ✔ | ||||
#[proptest] |
✔ | |||||
#[proptest(异步= ...)] |
✔ | |||||
#[proptest(转储)] |
✔ |
#[derive(Arbitrary)]
您可以通过在结构体或枚举声明中添加 #[derive(Arbitrary)]
来自动实现 proptest::arbitrary::Arbitrary
。
默认情况下,所有字段都使用通过 proptest::arbitrary::any()
获取的策略设置。
因此,以下两段代码是等效的。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
x: u32,
y: u32,
}
use proptest::{
arbitrary::{any, Arbitrary},
strategy::{BoxedStrategy, Strategy},
};
#[derive(Debug)]
struct TestInput {
x: u32,
y: u32,
}
impl Arbitrary for TestInput {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
let x = any::<u32>();
let y = any::<u32>();
(x, y).prop_map(|(x, y)| Self { x, y }).boxed()
}
}
#[策略]
您可以通过在字段中添加 #[strategy(...)]
来指定生成字段值的策略。
在以下示例中,字段 x
的值将小于 20。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[strategy(0..20u32)]
x: u32,
}
在 #[strategy]
中,可以通过 #
后跟字段名称来使用其他字段的值。
在以下示例中,y
的值小于或等于 x
。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
x: u32,
#[strategy(0..=#x)]
y: u32,
}
#[任意]
您不需要写 #[strategy(any_with::<Type>(expr))]
,可以直接写 #[any(expr)]
。
use proptest::collection::size_range;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
struct TestInput {
#[any(size_range(0..16).lift())]
x: Vec<u16>,
}
您不需要传递给 any_with
的表达式,只需写上要更改默认值的字段值即可。
因此,以下 TestInputA
、TestInputB
和 TestInputC
是等效的。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInputA {
#[any(InnerArgs { upper : 20, ..InnerArgs::default() })]
a: Inner,
}
#[derive(Arbitrary, Debug)]
struct TestInputB {
#[any(InnerArgs::default(), upper = 20)]
a: Inner,
}
#[derive(Arbitrary, Debug)]
struct TestInputC {
#[any(upper = 20)]
a: Inner,
}
#[derive(Default)]
struct InnerArgs {
lower: i32,
upper: i32,
}
#[derive(Arbitrary, Debug)]
#[arbitrary(args = InnerArgs)]
struct Inner {
#[strategy(args.lower..args.upper)]
x: i32,
}
#[权重]
默认情况下,所有变体以相等的概率出现。
您可以在变体上添加 #[weight]
来改变变体出现的概率。
在以下示例中,TestInput::B
出现的概率是 TestInput::A
的两倍。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
enum TestInput {
A,
#[weight(2)]
B,
}
如果您将 #[weight(0)]
添加到一个变体中,该变体将不会显示,因此您可以在此变体中使用不能用作 Arbitrary
的类型。
use test_strategy::Arbitrary;
#[derive(Debug)]
struct NotArbitrary;
#[derive(Arbitrary, Debug)]
enum TestInput {
A,
#[allow(dead_code)]
#[weight(0)] // Removing this `#[weight(0)]` will cause a compile error.
B(NotArbitrary),
}
#[映射]
在 #[strategy(...)]
中使用 prop_map
代替,可以使用 #[map(...)]
。
以下代码表示相同的意思。
use proptest::arbitrary::any;
use proptest::strategy::Strategy;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput1 {
#[strategy(any::<u32>().prop_map(|x| x + 1))]
x: u32,
}
#[derive(Arbitrary, Debug)]
struct TestInput2 {
#[strategy(any::<u32>())]
#[map(|x| x + 1)]
x: u32,
}
#[derive(Arbitrary, Debug)]
struct TestInput3 {
#[map(|x: u32| x + 1)]
x: u32,
}
对应用于 prop_map
或 #[map(...)]
的函数中的其他字段的引用将生成不同的策略。
在 #[strategy(...)]
中引用另一个字段会将它扩展为 prop_flat_map
,即使它在 prop_map
中。
use proptest::arbitrary::any;
use proptest::strategy::{Just, Strategy};
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct T1 {
x: u32,
#[strategy(any::<u32>().prop_map(move |y| #x + y))]
y: u32,
}
// The code above generates the following strategy.
let t1 = any::<u32>()
.prop_flat_map(|x| (Just(x), any::<u32>().prop_map(move |y| x + y)))
.prop_map(|(x, y)| T1 { x, y });
另一方面,如果您在 #[map]
中引用另一个字段,它将扩展为 prop_map
。
use proptest::arbitrary::any;
use proptest::strategy::Strategy;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct T2 {
x: u32,
#[map(|y: u32| #x + y)]
y: u32,
}
// The code above generates the following strategy.
let t2 = (any::<u32>(), any::<u32>()).prop_map(|(x, y)| T2 { x, y });
如果指定给 #[map]
的函数的输入和输出类型不同,则设置在 #[strategy]
中的策略的值类型是函数的输入类型,而不是字段类型。
use proptest::arbitrary::any;
use proptest::sample::Index;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct T1 {
#[strategy(any::<Index>())]
#[map(|i: Index| i.index(10))]
x: usize,
}
// `#[strategy(any::<Index>())]` can be omitted.
#[derive(Arbitrary, Debug)]
struct T2 {
#[map(|i: Index| i.index(10))]
x: usize,
}
#[过滤]
通过添加 #[filter]
,您可以限制生成的值。
在以下示例中,x 是一个偶数。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter(#x % 2 == 0)]
x: u32,
}
您也可以在谓词中使用多个变量。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter((#x + #y) % 2 == 0)]
struct T1 {
x: u32,
y: u32,
}
#[derive(Arbitrary, Debug)]
struct T2 {
x: u32,
#[filter((#x + #y) % 2 == 0)]
y: u32,
}
您可以使用结构或枚举的值在过滤器中使用,通过使用 #self
。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter((#self.x + #self.y) % 2 == 0)]
struct TestInput {
x: u32,
y: u32,
}
如果为 #[filter]
指定的表达式不包含名为在其自身字段名称后附加 # 的变量,则表达式被视为谓词函数,而不是返回 bool 的表达式。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter(is_even)]
x: u32,
}
fn is_even(x: &u32) -> bool {
x % 2 == 0
}
#[derive(Arbitrary, Debug)]
struct T2 {
a: u32,
// Since `#a` exists but `#b` does not, it is treated as a predicate function.
#[filter(|&x| x > #a)]
b: u32,
}
同样,如果一个表达式不包含 #self
,并且它附加到一个类型上,那么这个表达式被视为谓词函数。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
#[filter(is_even)]
struct T {
x: u32,
}
fn is_even(t: &T) -> bool {
t.x % 2 == 0
}
您可以通过将两个参数传递给 #[filter]
来指定过滤器名称。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[filter("x is even", #x % 2 == 0)]
x: u32,
}
#[by_ref]
默认情况下,如果您使用带有 #[strategy]
、#[any]
、#[map]
或 #[filter]
的变量,并且它被 #
附加,则复制的值将被设置。
将 #[by_ref]
添加到字段使其使用引用而不是复制的值。
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInput {
#[by_ref]
#[strategy(1..10u32)]
x: u32,
#[strategy(0..*#x)]
y: u32,
}
#[任意]
#[任意(参数=T)]
指定 Arbitrary::Parameters
的类型。
您可以使用此类型的 Rc
值在 #[strategy]
、#[any]
或 #[filter]
中,变量名为 args
。
use test_strategy::Arbitrary;
#[derive(Debug, Default)]
struct TestInputArgs {
x_max: u32,
}
#[derive(Arbitrary, Debug)]
#[arbitrary(args = TestInputArgs)]
struct TestInput {
#[strategy(0..=args.x_max)]
x: u32,
}
#[任意(界限(T1,T2, ..))]
默认情况下,如果未指定带有 #[strategy]
的字段的类型包含泛型参数,则该类型设置为特质界限。
因此,以下 TestInputA
和 TestInputB
是等价的。
use proptest::{
arbitrary::any, arbitrary::Arbitrary, strategy::BoxedStrategy, strategy::Strategy,
};
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug)]
struct TestInputA<T> {
x: T,
}
#[derive(Debug)]
struct TestInputB<T> {
x: T,
}
impl<T: Arbitrary + 'static> Arbitrary for TestInputB<T> {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<T>().prop_map(|x| Self { x }).boxed()
}
}
带有 #[strategy]
的字段的类型不会自动设置特质界限,因此您需要使用 #[arbitrary(bound(T))]
手动设置特质界限。
use proptest::arbitrary::any_with;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
#[arbitrary(bound(T))]
struct TestInput<T> {
#[strategy(any_with::<T>(Default::default()))]
x: T,
}
您也可以指定谓词而不是类型。
use proptest::arbitrary::{any_with, Arbitrary};
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
#[arbitrary(bound(T : Arbitrary + 'static))]
struct TestInput<T> {
#[strategy(any_with::<T>(Default::default()))]
x: T,
}
..
表示自动生成的特质界限。
以下示例在自动生成的特质界限之外使用手动指定的特质界限。
use proptest::arbitrary::any_with;
use test_strategy::Arbitrary;
#[derive(Arbitrary, Debug, PartialEq)]
#[arbitrary(bound(T1, ..))]
struct TestInput<T1, T2> {
#[strategy(any_with::<T1>(Default::default()))]
x: T1,
y: T2,
}
#[任意(转储)]
这会导致编译错误,并将由 #[derive(Arbitrary)]
生成的代码作为错误信息输出。
#[proptest]
当定义属性测试时,使用 #[proptest]
属性来代替 #[test]
。
以下示例定义了一个测试,该测试接受各种整数作为输入。
use test_strategy::proptest;
#[proptest]
fn my_test(_input: i32) {
// ...
}
您可以将 #[strategy]
、#[any]
、#[filter]
、#[by_ref]
添加到 # [proptest]
函数的参数中。
use test_strategy::proptest;
#[proptest]
fn my_test2(#[strategy(10..20)] _input: i32) {
// ...
}
您可以通过将 #[proptest]
属性的参数设置为 proptest::prelude::ProptestConfig
类型值来更改属性测试的配置。
use proptest::prelude::ProptestConfig;
use test_strategy::proptest;
#[proptest(ProptestConfig { cases : 1000, ..ProptestConfig::default() })]
fn my_test_with_config(_input: i32) {
// ...
}
与 #[any]
一样,您也可以只设置要更改的字段的值,而不是默认值。
以下示例与上述示例等效。
use proptest::prelude::ProptestConfig;
use test_strategy::proptest;
#[proptest(ProptestConfig::default(), cases = 1000)]
fn my_test_with_config_2(_input: i32) {
// ...
}
#[proptest(cases = 1000)]
fn my_test_with_config_3(_input: i32) {
// ...
}
#[proptest(异步= ...)]
可以通过将 async = ...
设置为 #[proptest]
的参数来测试异步函数。
async =
之后允许以下值。该值指定用于测试的异步运行时。
- "tokio"
[dev-dependencies]
test-strategy = "0.4.0"
proptest = "1.5.0"
tokio = { version = "1.38.0", features = ["rt-multi-thread"] }
use test_strategy::proptest;
use proptest::prop_assert;
#[proptest(async = "tokio")]
async fn my_test_async() {
async { }.await;
prop_assert!(true);
}
#[proptest(转储)]
您可以使用 #[proptest(dump)]
和将 #[proptest]
生成的代码作为编译错误信息输出。
#[proptest(dump)]
fn my_test(_input: i32) {
// ...
}
许可证
本项目采用 Apache-2.0/MIT 双重许可。有关详细信息,请参阅两个 LICENSE-* 文件。
贡献
除非您明确表示,否则根据 Apache-2.0 许可证定义的您提交的任何旨在包含在本作品中的贡献,均将按上述方式双重许可,而不附加任何额外条款或条件。
依赖关系
~0.3–0.8MB
~19K SLoC