任意用flatMap创建不考虑过滤器
Arbitrary created with flatMap does not consider the filter
我正在尝试 jqwik(版本 1.5.1),我从 documentation 中了解到我可以创建一个 Arbitrary
,其生成的值取决于另一个 Arbitrary
提供的值,具体使用 flatMap
函数。
我的实际目标不同,但基于这个想法:我需要 2 个 Arbitrary
始终为单个测试生成不同的值。这是我试过的:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
var secondArbitrary = firstArbitrary.flatMap(first ->
Arbitraries.integers().between(1, Integer.MAX_VALUE).filter(i -> !i.equals(first)));
return Combinators.combine(firstArbitrary, secondArbitrary).as(Tuple::of);
}
@Property
public void test(@ForAll("getValues") Tuple.Tuple2<Integer, Integer> values) {
assertThat(values.get1()).isNotEqualTo(values.get2());
}
并且此示例立即失败:
Shrunk Sample (1 steps)
-----------------------
arg0: (1, 1)
当然是AssertionError
:
java.lang.AssertionError:
Expecting:
1
not to be equal to:
1
我预计 filter
函数足以排除 firstArbitrary
产生的生成值,但似乎甚至没有考虑它,或者更有可能它做了其他事情。我错过了什么?有没有更简单的方法来确保给定一定数量的 integer
生成器,它们总是产生不同的值?
一个生成的值通过 flatMap
影响下一代步骤的一般想法是正确的。你缺少的是你通过组合平面映射范围的 firstArbitrary
和 secondArbitrary
outside 来松开这种耦合。修复是次要的:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.flatMap(
first -> Arbitraries.integers().between(1, Integer.MAX_VALUE)
.filter(i -> !i.equals(first))
.map(second -> Tuple.of(first, second))
);
}
那就是说还有更多 - 我认为更简单 - 实现目标的方法:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.tuple2().filter(t -> !t.get1().equals(t.get2()));
}
这摆脱了平面映射,这意味着在为 jqwik 收缩时更省力。
另一种可能的解决方案:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.list().ofSize(2).uniqueElements().map(l -> Tuple.of(l.get(0), l.get(1)));
}
这个可能看起来有点复杂,但它的优点是没有使用平面映射和过滤。过滤通常会降低生成、边缘情况、穷举生成和收缩的性能。这就是为什么我尽可能避免过滤而不会有太多麻烦。
我正在尝试 jqwik(版本 1.5.1),我从 documentation 中了解到我可以创建一个 Arbitrary
,其生成的值取决于另一个 Arbitrary
提供的值,具体使用 flatMap
函数。
我的实际目标不同,但基于这个想法:我需要 2 个 Arbitrary
始终为单个测试生成不同的值。这是我试过的:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
var secondArbitrary = firstArbitrary.flatMap(first ->
Arbitraries.integers().between(1, Integer.MAX_VALUE).filter(i -> !i.equals(first)));
return Combinators.combine(firstArbitrary, secondArbitrary).as(Tuple::of);
}
@Property
public void test(@ForAll("getValues") Tuple.Tuple2<Integer, Integer> values) {
assertThat(values.get1()).isNotEqualTo(values.get2());
}
并且此示例立即失败:
Shrunk Sample (1 steps)
-----------------------
arg0: (1, 1)
当然是AssertionError
:
java.lang.AssertionError:
Expecting:
1
not to be equal to:
1
我预计 filter
函数足以排除 firstArbitrary
产生的生成值,但似乎甚至没有考虑它,或者更有可能它做了其他事情。我错过了什么?有没有更简单的方法来确保给定一定数量的 integer
生成器,它们总是产生不同的值?
一个生成的值通过 flatMap
影响下一代步骤的一般想法是正确的。你缺少的是你通过组合平面映射范围的 firstArbitrary
和 secondArbitrary
outside 来松开这种耦合。修复是次要的:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.flatMap(
first -> Arbitraries.integers().between(1, Integer.MAX_VALUE)
.filter(i -> !i.equals(first))
.map(second -> Tuple.of(first, second))
);
}
那就是说还有更多 - 我认为更简单 - 实现目标的方法:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.tuple2().filter(t -> !t.get1().equals(t.get2()));
}
这摆脱了平面映射,这意味着在为 jqwik 收缩时更省力。
另一种可能的解决方案:
@Provide
private Arbitrary<Tuple.Tuple2<Integer, Integer>> getValues() {
var firstArbitrary = Arbitraries.integers().between(1, Integer.MAX_VALUE);
return firstArbitrary.list().ofSize(2).uniqueElements().map(l -> Tuple.of(l.get(0), l.get(1)));
}
这个可能看起来有点复杂,但它的优点是没有使用平面映射和过滤。过滤通常会降低生成、边缘情况、穷举生成和收缩的性能。这就是为什么我尽可能避免过滤而不会有太多麻烦。