5个不稳定版本
0.3.1 | 2022年2月27日 |
---|---|
0.3.0 | 2021年9月10日 |
0.2.1 | 2020年12月5日 |
0.2.0 | 2020年9月27日 |
0.1.0 | 2019年1月12日 |
#368 in 测试
303 每月下载量
用于 diceprop
305KB
6.5K SLoC
dicetest
使用随机生成的测试数据编写测试的框架。
此crate的状态
作者认为这个crate还不够稳定。更改将在变更日志中记录。
示例
以下是一个使用dicetest测试的错误排序函数的示例
fn bubble_sort<T: Ord>(slice: &mut [T]) {
let len = slice.len();
for _ in 0..len {
for j in 1..len - 1 {
let jpp = j + 1;
if slice[j] > slice[jpp] {
slice.swap(j, jpp);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dicetest::prelude::*;
#[test]
fn result_of_bubble_sort_is_sorted() {
Dicetest::repeatedly().run(|mut fate| {
let mut v = fate.roll(dice::vec(dice::u8(..), ..));
hint!("unsorted: {:?}", v);
bubble_sort(&mut v);
hint!(" sorted: {:?}", v);
let is_sorted = v.windows(2).all(|w| w[0] <= w[1]);
assert!(is_sorted);
})
}
}
运行 cargo test
产生以下输出
The test failed after 31 passes.
# Config
- seed: 3713861809241954222
- start limit: 0
- end limit: 100
- passes: 200
# Counterexample
- run code: "/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA=="
- limit: 3
- hints:
- unsorted: [201, 209, 2]
- sorted: [201, 2, 209]
- error: assertion failed: is_sorted
您可以通过设置环境变量来重新运行反例
DICETEST_DEBUG=/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA== cargo test
或者您也可以修改测试
Dicetest::debug("/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA==").run(|mut fate| {
// ...
})
功能
以下功能是可用的
- 许多libstd类型的生成器(
u8
、String
、Vec
等)。 - 函数的生成器(
FnMut
、FnOnce
、Fn
)。 - 生成器组合器(
map
、flat_map
、zip
等)。 - 与
rand::distributions::Distribution
的集成。 - 与
quickcheck::Arbitrary
的集成(不包含缩小)。 - 可配置的测试运行器。
- 测试调试工具(
hints
和stats
)。
以下功能是缺少的
- 反例缩小。
- 自定义伪随机数生成器。
- 为任意类型创建自己的类型类。
替代方案
- 写下您的测试数据并使用循环。
- 使用crate quickcheck。
- 使用crate proptest。
指南
本节将指导您了解dicetest最重要的概念和功能。
伪随机性
类型 Seed
允许确定 伪随机数。您可以使用固定的 Seed
或随机的 Seed
use dicetest::Seed;
println!("{:?}", Seed(42));
// Output: Seed(42)
println!("{:?}", Seed::random());
// Output: Seed(8019292413750407764)
Seed
可以用于初始化 伪随机数生成器 Prng
。对于每个 Seed
,Prng
都提供了一组不同的无限伪随机序列的 u64
use dicetest::{Prng, Seed};
fn print_random_values(mut prng: Prng) {
for _ in 0..3 {
print!("{:?}, ", prng.next_number());
}
println!("...");
}
print_random_values(Prng::from_seed(Seed(42)));
// Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ...
print_random_values(Prng::from_seed(Seed(42)));
// Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ...
print_random_values(Prng::from_seed(Seed::random()));
// Output: 4221507577048064061, 15374206214556255352, 4977687432463843847, ...
print_random_values(Prng::from_seed(Seed::random()));
// Output: 11086225885938422405, 9312304973013875005, 1036200222843160301, ...
骰子
尽管 Prng
只能生成伪随机 u64
,但 u64
可以用于构建更复杂的数据。特性 DieOnce
和 Die
代表基于 Prng
的任何类型的值生成器。
DieOnce
的实现者是一个只能使用一次的生成器(类似于 FnOnce
)。
use dicetest::prelude::*;
let xx = "xx".to_string();
let yy = "yy".to_string();
// This generator implements `DieOnce`.
// It chooses one of the `String`s without cloning them.
let xx_or_yy_die = dice::one_of_once().two(xx, yy);
Die
的实现者是一个可以无限次使用的生成器(类似于 Fn
)。
use dicetest::prelude::*;
let xx = "xx".to_string();
let yy = "yy".to_string();
// This generator implements `Die`.
// It chooses one of the `String`s by cloning them.
let xx_or_yy_die = dice::one_of().two(xx, yy);
// This generator uses `xx_or_yy_die` to generate three `String`s at once.
let three_xx_or_yy_die = dice::array::<_, _, 3>(xx_or_yy_die);
生成器可以轻松实现和组合
use dicetest::prelude::*;
// A classic die that generates a number between 1 and 6 with uniform distribution.
let classic_die = dice::one_of().six::<u8>(1, 2, 3, 4, 5, 6);
// A loaded die that generates the number 6 more frequently.
let loaded_die =
dice::weighted_one_of().six::<u8>((1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 6));
// This die generates the result of the function.
let die_from_fn = dice::from_fn(|_| 42);
// This die generates always the same `String` by cloning the original one.
let foo_die = dice::just("foo".to_string());
// This die generates an arbitrary byte.
let byte_die = dice::u8(..);
// This die generates a non-zero byte.
let non_zero_byte_die = dice::u8(1..);
// This die generates a `Vec` that contains an arbitrary number of arbitrary bytes.
let bytes_die = dice::vec(dice::u8(..), ..);
// This die generates a `Vec` that contains up to 10 arbitrary bytes.
let up_to_ten_bytes_die = dice::vec(dice::u8(..), ..=10);
// This die generates an arbitrary wrapped byte.
struct WrappedByte(u8);
let wrapped_byte_die = dice::u8(..).map(WrappedByte);
// This die generates a permutation of `(0..=n)` for an arbitrary `n`.
let permutation_die = dice::length(0..).flat_map(|n| {
let vec = (0..=n).collect::<Vec<_>>();
dice::shuffled_vec(vec)
});
结构体 Fate
是使用 DieOnce
或 Die
所必需的。它包含两个参数
Prng
:为DieOnce
或Die
的实现者提供伪随机u64
,以构建更复杂的数据。实现者应仅将其用作其随机性的来源。Limit
:实现者生成的动态数据结构长度的上限。实现者可以自由解释或甚至忽略此值。
use dicetest::prelude::*;
use dicetest::{Limit, Prng};
// Provides the randomness for the generator and will be mutated when used.
let mut prng = Prng::from_seed(0x5EED.into());
// Limits the length of dynamic data structures. The generator has only read access.
let limit = Limit(5);
// Contains all parameters necessary for using `DieOnce` or `Die`.
let mut fate = Fate::new(&mut prng, limit);
// Generator for a `Vec` with an arbitrary length.
let vec_die = dice::vec(dice::u8(..), ..);
// Generates a `Vec`. Although `vec_die` can generate a `Vec` with an arbitrary length,
// the length of the actual `Vec` is limited by `limit`.
let vec = fate.roll(vec_die);
assert!(vec.len() <= 5);
println!("{:?}", vec);
// Output: [252, 231, 153, 0]
测试
如果您想编写使用随机生成的测试数据的测试,可以使用测试构建器 Dicetest
- 它可以通过源代码或环境变量进行配置。
- 它使用不同的种子重复运行您的测试。
- 它记录有用的信息,有助于您调试测试。
use dicetest::prelude::*;
#[test]
fn test_foo() {
// Runs your test with default configuration.
Dicetest::repeatedly().run(|fate| {
// Write your test here.
});
}
#[test]
fn test_bar() {
// Runs your test with custom configuration.
Dicetest::repeatedly().passes(10000).run(|fate| {
// Write your test here.
});
}
闭包包含您的测试。使用传递的 fate
,您可以生成测试数据并进行断言。如果闭包崩溃,Dicetest
会捕获崩溃,将测试结果记录到 stdout 并恢复崩溃。
提示
提示可以用于分析单个测试运行。在大多数情况下,您想分析反例。使用它来揭示哪些测试数据被生成或哪些分支被采取
use dicetest::prelude::*;
#[test]
fn test_foo() {
Dicetest::repeatedly().run(|mut fate| {
let x = fate.roll(dice::u8(1..=5));
hint_debug!(x);
let y = fate.roll(dice::u8(1..=3));
if y != x {
hint!("took branch if with y = {}", y);
assert_eq!(3, y);
} else {
hint!("took branch else");
}
})
}
运行测试产生以下输出
The test failed after 0 passes.
# Config
- seed: 10929669535587280453
- start limit: 0
- end limit: 100
- passes: 200
# Counterexample
- run code: "JfXG0LRXjKUMu+YmdrF38/GstRdeLAeMRTKskCQcgNoAAAAAAAAAAA=="
- limit: 0
- hints:
- x = 5
- took branch if with y = 1
- error: assertion failed: `(left == right)`
left: `3`,
right: `1`
统计数据
统计数据可以用于分析多个测试运行。使用它来揭示生成测试数据的分布或分支的概率
use dicetest::prelude::*;
#[test]
fn test_foo() {
Dicetest::repeatedly().run(|mut fate| {
let x = fate.roll(dice::u8(1..=5));
stat_debug!(x);
let y = fate.roll(dice::u8(1..=3));
if y != x {
stat!("branch", "if with y = {}", y)
} else {
stat!("branch", "else");
}
})
}
使用环境变量 DICETEST_STATS_ENABLED=true
运行测试产生以下输出
The test withstood 200 passes.
# Config
- seed: 5043079553183914912
- start limit: 0
- end limit: 100
- passes: 200
# Stats
- branch:
- 29.50% (59): if with y = 1
- 27.50% (55): if with y = 3
- 22.50% (45): if with y = 2
- 20.50% (41): else
- x:
- 31.50% (63): 1
- 22.00% (44): 5
- 17.00% (34): 2
- 15.50% (31): 4
- 14.00% (28): 3
环境变量
您可以使用环境变量来配置测试而无需更改源代码。有关受支持的环境变量的完整列表,请参阅 Dicetest
的文档。以下是一些示例
- 您想使用其运行代码(从测试结果复制)调试
mytest
的反例
DICETEST_DEBUG=ABIDje/+CYVkmmCVTwKJ2go6VrzZWMjO2Bqc9m3b3h0DAAAAAAAAAA== cargo test mytest
- 您想用其种子(从测试结果复制)重现
mytest
的结果
DICETEST_SEED=795359663177100823 cargo test mytest
- 您想查看
mytest
的统计数据
DICETEST_STATS_ENABLED=true cargo test -- --show-output mytest
- 您想用更多的迭代次数和更大的测试数据运行
mytest
DICETEST_PASSES_MULTIPLIER=10 DICETEST_LIMIT_MULTIPLIER=2 cargo test mytest
- 您想用单个测试运行运行
mytest
并查看测试结果
DICETEST_MODE=once cargo test -- --show-output mytest
功能标志
存在几个功能标志,可以在编译时禁用运行时开销或启用附加功能。
hints
(默认启用)
在编译时启用或禁用提示功能。如果禁用,所有提示操作都是无操作。
stats
(默认启用)
在编译时启用或禁用统计功能。如果禁用,所有统计操作都是无操作。
rand_core
(默认禁用)
如果启用,dicetest::Prng
和 dicetest::Fate
实现了 rand_core::RngCore
特性。
rand_full
(默认禁用,为 rand_core,rand
的别名)
如果启用,Fate::roll_distribution
和 dice::from_distribution
可用。这允许从 rand::distributions::Distribution
的实现生成值和创建 Die
。
use dicetest::prelude::*;
use dicetest::{Limit, Prng};
let mut prng = Prng::from_seed(0x5EED.into());
let limit = Limit(5);
let mut fate = Fate::new(&mut prng, limit);
// Generate a value from a `rand::distributions::Distribution`
let byte: u8 = fate.roll_distribution(rand::distributions::Standard);
println!("{:?}", byte);
// Output: 28
// Create a `Die` from a `rand::distributions::Distribution`
let byte_die = dice::from_distribution(rand::distributions::Standard);
let bytes_die = dice::vec(byte_die, 1..);
let bytes: Vec<u8> = fate.roll(bytes_die);
println!("{:?}", bytes);
// Output: [236, 205, 151, 229]
quickcheck_full
(默认禁用,为 rand_core,quickcheck
的别名)
如果启用,Fate
实现了 quickcheck::Gen
特性,并且 Fate::roll_arbitrary
和 dice::arbitrary
可用。这允许为实现了 quickcheck::Arbitrary
的类型生成值和创建 Die
。
use dicetest::prelude::*;
use dicetest::{Limit, Prng};
let mut prng = Prng::from_seed(0x5EED.into());
let limit = Limit(5);
let mut fate = Fate::new(&mut prng, limit);
// Generate a value of a type that implements `quickcheck::Arbitrary`
let byte: u8 = fate.roll_arbitrary();
println!("{:?}", byte);
// Output: 0
// Create a `Die` for a type that implements `quickcheck::Arbitrary`
let byte_die = dice::arbitrary();
let bytes_die = dice::vec(byte_die, 1..);
let bytes: Vec<u8> = fate.roll(bytes_die);
println!("{:?}", bytes);
// Output: [1, 4, 4, 2]
许可证
根据您选择的以下任一项获得许可:
- Apache License,版本 2.0(LICENSE-APACHE 或 http://www.apache.org/licenses/LICENSE-2.0)
- MIT 许可证(LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确表示,否则根据 Apache-2.0 许可证定义的,您有意提交给作品的所有贡献,均应按上述方式双重许可,没有任何额外的条款或条件。
依赖项
~74–370KB