简体   繁体   中英

Arbitrary created with flatMap does not consider the filter

I am trying jqwik (version 1.5.1) and I read from the documentation that I can create an Arbitrary whose generated value depends on the one supplied by another Arbitrary , specifically using the flatMap function.

My actual goal is different, but based on this idea: I need 2 Arbitrary s that always generate different values for a single test. This is what I tried:

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

And it immediately fails with this sample:

Shrunk Sample (1 steps)
-----------------------
  arg0: (1, 1)

Throwing an AssertionError of course:

java.lang.AssertionError: 
Expecting:
  1
not to be equal to:
  1

I expected the filter function would have been enough to exclude the generated value produced by the firstArbitrary but it seems like it is not even considered, or more likely it does something else. What am I missing? Is there an easier way to make sure that, given a certain number of integer generators, they always produce different values?

The general idea of one generated value influencing the next generation step through flatMap is right. The thing you are missing is that you loose this coupling by combining firstArbitrary and secondArbitrary outside of the flat mapping scope. The fix is minor:

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

That said there are more - I'd argue simpler - ways to achieve your goal:

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

This gets rid of flat mapping, which means less effort while shrinking for jqwik.

Another possible solution:

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

This one might seem a bit involved, but it has the advantage that no flat mapping and no filtering is being used. Filtering often reduces performance of generation, edge cases, exhaustive generation and shrinking. That's why I steer clear of filtering whenever I can without too much hassle.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM