使用Jqwik有状态测试时Action.Dependent链抛出JqwikException问题
解决Jqwik有状态测试中空动作链的问题
针对你遇到的动作链可能全选非生产性动作导致空链的问题,有几个直接有效的解决方案:
1. 强制动作链以生产性动作开头
最直接的方式是用ActionChain.startWith()指定必须先执行一个生产性动作,后续再随机选择其他动作(包括依赖状态的动作和生产性动作)。这样从根源上保证动作链至少包含一个有效动作,不会为空。
举个栈测试的代码示例:
// 定义生产性push动作(始终可执行) Action<Stack<Integer>> pushAction = Action.start("push", stack -> { Arbitrary<Integer> values = Arbitraries.integers(); return values.map(value -> s -> { s.push(value); return s; }); }); // 定义依赖状态的pop动作(栈非空时才可用) Action<Stack<Integer>> popAction = Action.when("pop", stack -> !stack.isEmpty()) .then(stack -> Arbitraries.just(s -> { s.pop(); return s; })); // 构建保证至少有一个生产性动作的动作链 @Provide Arbitrary<ActionChain<Stack<Integer>>> validStackActionChains() { return ActionChain.startWith(pushAction) .addAction(pushAction, 2) // 可选:给push动作加权重,提高选中概率 .addAction(popAction); } // 测试用例 @Property void stackOperationsTest(@ForAll("validStackActionChains") ActionChain<Stack<Integer>> chain) { chain.run(Stack::new); }
这种方式的核心是startWith()强制第一个动作是生产性的,后续动作可以正常随机选择,既保证了链非空,又保留了测试的随机性。
2. 自定义动作选择器确保至少选一个生产性动作
如果需要更灵活的控制(比如生产性动作不一定是第一个),可以实现自定义ActionSelector,在选择动作时跟踪是否已选过生产性动作:
- 如果还没选过生产性动作,只从可用的生产性动作中选择
- 一旦选过生产性动作,再正常混合选择所有可用动作
示例代码:
class AtLeastOneProductiveSelector implements ActionSelector { private final List<Action<?>> productiveActions; private final List<Action<?>> allActions; private boolean hasPickedProductive = false; public AtLeastOneProductiveSelector(List<Action<?>> productive, List<Action<?>> all) { this.productiveActions = productive; this.allActions = all; } @Override public <S> Arbitrary<Action<S>> select(ActionChain<S> chain) { List<Action<S>> applicableActions; if (!hasPickedProductive) { // 未选过生产性动作,仅从可用的生产性动作中选 applicableActions = productiveActions.stream() .filter(action -> action.isApplicable(chain.currentState())) .map(action -> (Action<S>) action) .collect(Collectors.toList()); } else { // 已选过,正常选所有可用动作 applicableActions = allActions.stream() .filter(action -> action.isApplicable(chain.currentState())) .map(action -> (Action<S>) action) .collect(Collectors.toList()); } return Arbitraries.of(applicableActions); } @Override public void reset() { hasPickedProductive = false; } @Override public void actionSelected(Action<?> action) { if (productiveActions.contains(action)) { hasPickedProductive = true; } } }
使用自定义选择器构建动作链:
@Provide Arbitrary<ActionChain<Stack<Integer>>> validStackActionChains() { List<Action<Stack<Integer>>> productive = List.of(pushAction); List<Action<Stack<Integer>>> allActions = List.of(pushAction, popAction); return ActionChain.<Stack<Integer>>create() .withActionSelector(new AtLeastOneProductiveSelector(productive, allActions)) .addActions(allActions); }
为什么independent()+endOfChain()没用?
ActionChain.independent()只是让动作选择不依赖之前的动作执行结果,但无法过滤掉全选非生产性动作的情况;endOfChain()仅用于控制链的结束条件,不能保证链中包含生产性动作,所以这两个组合解决不了你的核心问题。
内容的提问来源于stack exchange,提问作者Michał Kłeczek
相关产品推荐
相关产品推荐

