如何将任意 proptest 值的转换排除在测试用例主体之外?
How can I keep the transformation of arbitrary proptest values out of the test case body?
我想彻底测试两个 BTreeSet
的交集的实现。我可以写:
use self::proptest::prelude::*;
proptest! {
#[test]
fn intersect_this(s1: BTreeSet<i32>, s2: BTreeSet<i32>) {
// ...
}
}
但这代码覆盖率很差,因为代码专门针对某些随机集不太可能命中的情况。一种特殊情况是其元素范围几乎不相交的集合(一个集合的值 <= x,另一个集合的值 >= x)。在 Python with hypothesis 中(我不是新手),我会写:
from hypothesis import given
from hypothesis.strategies import builds, integers, sets
from typing import Set
def touching_ranges(elements: Set[int], split: int):
return {elt for elt in elements if elt < split}.union({split}), \
{elt for elt in elements if elt > split}.union({split})
@given(builds(touching_ranges, sets(integers()), integers()))
def test_touching_ranges(sets):
s1, s2 = sets
assert len(s1.intersection(s2)) == 1
在 Rust 中,我只是把所有东西都塞进了体内:
#[test]
fn touching(mut s1: BTreeSet<i32>, split: i32) {
let mut s2 = s1.split_off(&split);
s1.insert(split);
s2.insert(split);
prop_assert_eq!(s1.intersection(&s2).count(), 1);
}
如何将任意值的转换排除在测试用例主体之外?我无法理解我找到的关于策略的任何代码示例,而且 Stack Overflow 几乎没有与 proptest 相关的问题。
proptest
中内置了BTreeSetStrategy
,所以比较直接:
use proptest::prelude::*;
use std::collections::BTreeSet;
prop_compose! {
fn touching_ranges()
(split: i32,
mut s1: BTreeSet<i32>)
-> (BTreeSet<i32>, BTreeSet<i32>)
{
let mut s2 = s1.split_off(&split);
s1.insert(split);
s2.insert(split);
(s1, s2)
}
}
proptest! {
#[test]
fn touching((s1, s2) in touching_ranges()) {
assert_eq!(s1.intersection(&s2).count(), 1);
}
}
这里的一些语法不是普通的 Rust,因此可能需要进一步解释:
- 在
proptest!
宏内部,测试是普通的 Rust 函数,除了它们还可以访问 in Strategy
语法以生成输入。
- Proptest 策略是内置的或用户定义的。定义策略的一种方法是在
prop_compose!
宏中。同样,这是一个普通的 Rust 函数,只是它可以有两个参数列表。第一个参数列表是通常的输入;第二个可以使用第一个的 in Strategy
语法和参数。 return 类型指示正在生成的值的类型。在这种情况下,一个包含两个 BTreeSet
s. 的元组
- 您可能已经猜到了,Proptest crate 带有元组的
Strategy
实现,因此实现 Strategy
的类型元组本身就是 Strategy
。这就是函数 touching_ranges
可以作为一个函数使用的原因。
我想彻底测试两个 BTreeSet
的交集的实现。我可以写:
use self::proptest::prelude::*;
proptest! {
#[test]
fn intersect_this(s1: BTreeSet<i32>, s2: BTreeSet<i32>) {
// ...
}
}
但这代码覆盖率很差,因为代码专门针对某些随机集不太可能命中的情况。一种特殊情况是其元素范围几乎不相交的集合(一个集合的值 <= x,另一个集合的值 >= x)。在 Python with hypothesis 中(我不是新手),我会写:
from hypothesis import given
from hypothesis.strategies import builds, integers, sets
from typing import Set
def touching_ranges(elements: Set[int], split: int):
return {elt for elt in elements if elt < split}.union({split}), \
{elt for elt in elements if elt > split}.union({split})
@given(builds(touching_ranges, sets(integers()), integers()))
def test_touching_ranges(sets):
s1, s2 = sets
assert len(s1.intersection(s2)) == 1
在 Rust 中,我只是把所有东西都塞进了体内:
#[test]
fn touching(mut s1: BTreeSet<i32>, split: i32) {
let mut s2 = s1.split_off(&split);
s1.insert(split);
s2.insert(split);
prop_assert_eq!(s1.intersection(&s2).count(), 1);
}
如何将任意值的转换排除在测试用例主体之外?我无法理解我找到的关于策略的任何代码示例,而且 Stack Overflow 几乎没有与 proptest 相关的问题。
proptest
中内置了BTreeSetStrategy
,所以比较直接:
use proptest::prelude::*;
use std::collections::BTreeSet;
prop_compose! {
fn touching_ranges()
(split: i32,
mut s1: BTreeSet<i32>)
-> (BTreeSet<i32>, BTreeSet<i32>)
{
let mut s2 = s1.split_off(&split);
s1.insert(split);
s2.insert(split);
(s1, s2)
}
}
proptest! {
#[test]
fn touching((s1, s2) in touching_ranges()) {
assert_eq!(s1.intersection(&s2).count(), 1);
}
}
这里的一些语法不是普通的 Rust,因此可能需要进一步解释:
- 在
proptest!
宏内部,测试是普通的 Rust 函数,除了它们还可以访问in Strategy
语法以生成输入。 - Proptest 策略是内置的或用户定义的。定义策略的一种方法是在
prop_compose!
宏中。同样,这是一个普通的 Rust 函数,只是它可以有两个参数列表。第一个参数列表是通常的输入;第二个可以使用第一个的in Strategy
语法和参数。 return 类型指示正在生成的值的类型。在这种情况下,一个包含两个BTreeSet
s. 的元组
- 您可能已经猜到了,Proptest crate 带有元组的
Strategy
实现,因此实现Strategy
的类型元组本身就是Strategy
。这就是函数touching_ranges
可以作为一个函数使用的原因。