【问题标题】:Arbitrary created with flatMap does not consider the filter使用 flatMap 创建的任意不考虑过滤器
【发布时间】:2021-07-01 12:12:43
【问题描述】:

我正在尝试 jqwik(版本 1.5.1),我从documentation 中读到,我可以创建一个Arbitrary,其生成的值取决于另一个Arbitrary 提供的值,特别是使用flatMap 函数.

我的实际目标不同,但基于这个想法:我需要 2 个Arbitrarys,它们总是为单个测试生成不同的值。这是我尝试过的:

@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 生成器,它们总是产生不同的值?

【问题讨论】:

    标签: java testing generator property-based-testing jqwik


    【解决方案1】:

    一个生成值通过flatMap 影响下一代步骤的总体思路是正确的。您缺少的是通过将firstArbitrarysecondArbitrary 在平面映射范围之外 组合在一起来松散这种耦合。修复是次要的:

    @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)));
    }
    

    这个可能看起来有点复杂,但它的优点是没有使用平面映射和过滤。过滤通常会降低生成、边缘情况、穷举生成和收缩的性能。这就是为什么我尽可能不费力地避开过滤。

    【讨论】:

    • 谢谢!不幸的是,我认为flatMapfilter 在我的情况下是必要的,因为我不能使用相同的Arbitrary 来生成Arbitrarys 的列表,因为第n 个的范围取决于前一个生成的值.或者我还能以某种方式避免它们吗?再次感谢您。
    • 如果存在真正的值耦合,则无法始终避免平面映射。否则它根本就不必存在。
    猜你喜欢
    • 2021-02-24
    • 1970-01-01
    • 1970-01-01
    • 2013-10-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多