在 Action 中使用 Arbitrary.sample 是否可重现?

Is it reproducible to use Arbitrary.sample from within an Action?

我们对订单系统进行了状态测试。有一个 Arbitrary 将生成一个 Order 对象,该对象具有多个 LineItem

有以下操作:

创建订单的动作采用订单本身,例如:

Arbitraries.defaultFor(Order.class).map(CreateOrderAction::new)

操作的状态了解所有已创建的订单。

要取消 LineItem,我们需要了解创建了哪些订单。在 CancelLineItemAction 中执行以下操作是否安全?

LineItem line = Arbitraries.<Collection<Order>>of(state.orders())
                           .flatMap(order -> Arbitraries.<Collection<LineItem>>of(order.lineItems()))
                           .sample();

基于 Arbitrary.sample() 的 javadoc,它 似乎 安全,但在状态测试文档中未明确提及此构造,我们也不想广泛使用它只是为了破坏我们测试的可重复性。

TLDR

  • Arbitrary.sample() 不是为那样使用而设计的
  • 我建议使用对订单项数量取模的随机取消索引

1。为什么不推荐Arbitrary.sample()

Arbitrary.sample() 被设计为在属性之外使用,例如试验生成的值或在 JUnit Jupiter 等其他上下文中使用它。至少有三个原因:

  • 用于生成值的基础随机种子取决于发生的情况 取样前。因此,结果并不是真正可重现的。
  • 抽样不会考虑任何添加的可能会改变内容的域上下文 正在生成。
  • sample() 生成的值不参与收缩

2。方案一:递交一个Random对象,用于生成

在生成 CancelLineItemAction 时提交一个 Random 实例:

Arbitraries.random().map(random -> new CancelLineItemAction(random))

使用随机数调用生成器:

LineItem line = Arbitraries.of(state.orders())
           .flatMap(order -> Arbitraries.of(order.lineItems()))
           .generator(100).next(random).value();

但实际上这与您想要做的事情有关。这是一个简化:

3。选项 2:提交一个 Random 对象并使用它来挑选订单项

同上但采样不走弯路:

List<LineItem> lineItems = state.orders().stream()
                                .flatMap(order -> order.lineItems().stream())
                                .collect(Collectors.toList());

int randomIndex = random.nextInt(lineItems.size());
LineItem line = lineItems.get(randomIndex);

选项 1 和 2 都(希望)在 jqwik 的生命周期中表现合理 但他们不会尝试任何收缩。这就是我推荐下一个选项的原因。

4。选项 3:提交取消索引并将其对行项目数取模

要生成操作:

Arbitraries.integer().between(0, MAX_LINE_ITEMS)
                     .map(cancelIndex -> new CancelLineItemAction(cancelIndex))

实际使用它:

List<LineItem> lineItems = state.orders().stream()
                                .flatMap(order -> order.lineItems().stream())
                                .collect(Collectors.toList());

int randomIndex = cancelIndex % lineItems.size();
LineItem line = lineItems.get(randomIndex);

此处更详细地描述了该方法:https://blog.johanneslink.net/2020/03/11/model-based-testing/

5。未来展望

在或多或少遥远的未来,jqwik 可能会允许在生成动作时提交当前状态。这将使像您这样的东西变得更简单。但是这个功能还没有被优先考虑。