我们对订单系统进行了有状态测试。有一个Arbitrary
将生成一个具有多个LineItem
的Order
对象。
有以下操作:
- 创建
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.为什么不建议使用任意.sample((
Arbitrary.sample()
被设计为在属性之外使用,例如,对生成的值进行实验或在JUnit Jupiter等其他环境中使用。至少有三个原因:
- 用于生成值的底层随机种子取决于发生的情况采样前。因此,结果并不是真正可重复的
- 采样不会考虑任何可能改变正在生成
sample()
生成的值不参与收缩
2.选项1:交一个随机对象并使用它生成
生成CancelLineItemAction:时交一个随机实例
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:交一个随机对象并使用它来拾取行项目
与上述相同,但采样时不要绕路:
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可能允许在生成动作时交出当前状态。这会让像你这样的东西变得简单一点。但这一功能尚未得到优先考虑。