Как написать метод генератора jqwik с вложенными генераторами

Используя jqwik.net, пытаемся сгенерировать класс Rule с вложенным классом RuleConfig внутри него. Класс RuleConfig имеет вложенный ruleProps, который является картой

Метод statusReturnedFromApplyingRule всегда возвращает инициализированное правило вместо использования значений метода @provide?? Возвращенное правило:rule:Rule{ruleId='null', inputMetricSelector=null, ruleConfig='RuleConfig{ruleType='null', ruleProps={}}'}, elements:[{}]

Вот мой код:

public class RangeMatchRuleTest {

    @Property
    @Report(Reporting.GENERATED)
    boolean statusReturnedFromApplyingRule(@ForAll("generateRule") Rule rule,
                                           @ForAll("generateInputMapElements") Iterable<Map<String, Object>> elements) {
        RangeMatchRule rangeMatchRule = new RangeMatchRule();
        final RuleIF.Status status = rangeMatchRule.applyRule(rule, elements);
        return RuleIF.getEnums().contains(status.toString());
    }

    @Provide
    Arbitrary<Rule> generateRule() {
        Rule rule = new Rule();
        RuleConfig ruleConfig = new RuleConfig();
        Map<String, Object> ruleProps = new HashMap<>();

        Arbitrary<Double> lowThresholdArb = Arbitraries.doubles()
                .between(0.0, 29.0);
        lowThresholdArb.allValues().ifPresent(doubleStream -> ruleProps.put(Utils.LOW_THRESHOLD, doubleStream.findFirst().get()));
        //lowThresholdArb.map(lowThreshold -> ruleProps.put(Utils.LOW_THRESHOLD, lowThreshold) );
        Arbitrary<Double> highThresholdArb = Arbitraries.doubles()
                .between(30.0, 50.0);
        highThresholdArb.map(highThreshold -> ruleProps.put(Utils.HIGH_THRESHOLD, highThreshold));
        ruleConfig.setRuleProps(ruleProps);
        rule.setRuleConfig(ruleConfig);
        return Arbitraries.create(() -> rule);
    }

    @Provide
    Arbitrary<Iterable<Map<String, Object>>> generateInputMapElements() {
        Arbitrary<Double> metricValueArb = Arbitraries.doubles()
                .between(0, 50.0);
        Map<String, Object> inputMap = new HashMap<>();
        metricValueArb.map(metricValue -> inputMap.put(Utils.METRIC_VALUE, metricValue));
        List<Map<String, Object>> inputMapLst = new ArrayList<>();
        inputMapLst.add(inputMap);
        return Arbitraries.create(() -> inputMapLst);
    }
}

TIA

2 ответа

Решение

Вы строите generateRule метод при неправильном предположении, что произвольный mapпри вызове выполнял любое реальное действие. Это не тот случай. Дело в том, чтоmap возвращает другой произвольный экземпляр, дает сильную подсказку.

Основная идея, которую вы должны понять, заключается в том, что метод поставщика - метод, помеченный @Provide- не что иное, как "описание" процесса генерации; он будет вызван только один раз. Фактическое создание объекта происходит впоследствии и контролируется фреймворком.

Вот переработанный generateRule метод, который должен делать то, что вы намеревались:

@Provide
Arbitrary<Rule> generateRule() {
    Arbitrary<Double> lowThresholdArb = Arbitraries.doubles()
                                                   .between(0.0, 29.0);
    Arbitrary<Double> highThresholdArb = Arbitraries.doubles()
                                                    .between(30.0, 50.0);

    Arbitrary<RuleConfig> configArb =
        Combinators.combine(lowThresholdArb, highThresholdArb)
                   .as((low, high) -> {
                       Map<String, Object> ruleProps = new HashMap<>();
                       ruleProps.put(Utils.LOW_THRESHOLD, low);
                       ruleProps.put(Utils.HIGH_THRESHOLD, high);
                       RuleConfig ruleConfig = new RuleConfig();
                       ruleConfig.setRuleProps(ruleProps);
                       return ruleConfig;
                   });

    return configArb.map(config -> {
        Rule rule = new Rule();
        rule.setRuleConfig(config);
        return rule;
    });
}

Надеюсь, вы видите, что создание генератора похоже на программирование потока данных: начиная с некоторых базовых арбитров - lowThresholdArb а также highThresholdArb- вы комбинируете, сопоставляете и фильтруете их. В конце концов, единственный экземплярArbitrary должен быть возвращен.

Кстати: если вы хотите, чтобы этот генератор применялся каждый раз, когда вам нужен Rule, вы можете написать следующий класс:

public class RuleArbitraryProvider implements ArbitraryProvider {

    @Override
    public boolean canProvideFor(TypeUsage targetType) {
        return targetType.isOfType(Rule.class);
    }

    @Override
    public Set<Arbitrary<?>> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
        return Collections.singleton(generateRule());
    }

    private Arbitrary<Rule> generateRule() {
        // Put here the code from above
        ...
    }
}

и зарегистрируйте его как провайдера по умолчанию.

Дополнительный пример для карты выше на основе предоставленного ответа:

    @Provide
Arbitrary<Iterable<Map<String, Object>>> generateInputMapElements() {
    Arbitrary<Double> metricValueArb = Arbitraries.doubles()
            .between(0, 50.0);

    Arbitrary<Map<String, Object>> inputMapArb =
            metricValueArb.map(metricsValue -> {
                Map<String, Object> inputMap = new HashMap<>();
                inputMap.put(Utils.METRIC_VALUE, metricsValue);
                return inputMap;
            });
    return inputMapArb.map(inputMap -> {
        List<Map<String, Object>> inputMapLst = new ArrayList<>();
        inputMapLst.add(inputMap);
        return inputMapLst;
    });
}
Другие вопросы по тегам