Произвольный, созданный с помощью flatMap, не учитывает фильтр

Я пытаюсь использовать jqwik (версия 1.5.1), и я прочитал из документации, что могу создать сгенерированное значение, значение которого зависит от значения, предоставленного другим, в частности, используя 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 генераторы, они всегда производят разные значения?

1 ответ

Решение

Общая идея одной созданной ценности, влияющей на следующее поколение шага через flatMapправильно. Вам не хватает того, что вы теряете эту связь, комбинируя firstArbitrary а также secondArbitrary вне области плоского отображения. Исправление незначительное:

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

Это может показаться немного сложным, но у него есть то преимущество, что не используется плоское сопоставление и фильтрация. Фильтрация часто снижает производительность генерации, крайние случаи, исчерпывающую генерацию и сжатие. Вот почему я избегаю фильтрации, когда могу, без особых хлопот.

Другие вопросы по тегам