Tenemos una prueba con estado para un sistema de pedidos. Hay un objeto Arbitrary que generará un objeto de Order que tiene una cantidad de LineItem de línea.
Hay acciones para:
OrderLineItemLa acción para crear una orden toma la orden en sí, por ejemplo:
Arbitraries.defaultFor(Order.class).map(CreateOrderAction::new)El estado de las acciones tiene conocimiento sobre todas las órdenes creadas.
Para cancelar un LineItem , necesitamos saber qué pedidos se crean. Dentro CancelLineItemAction , ¿es seguro hacer lo siguiente?
LineItem line = Arbitraries.<Collection<Order>>of(state.orders()) .flatMap(order -> Arbitraries.<Collection<LineItem>>of(order.lineItems())) .sample(); Basado en el javadoc de Arbitrary.sample() , parece seguro, pero esta construcción no se menciona explícitamente en la documentación sobre pruebas con estado, y no queremos usarla extensamente solo para romper la reproducibilidad de nuestras pruebas.
Arbitrary.sample() no está diseñado para usarse de esa manera Arbitrary.sample() está diseñado para usarse fuera de las propiedades, por ejemplo, para experimentar con valores generados o para usarlo en otros contextos como JUnit Jupiter. Hay al menos tres razones:
sample() NO PARTICIPAN EN LA REDUCCIÓNEntregue una instancia aleatoria al generar un CancelLineItemAction:
Arbitraries.random().map(random -> new CancelLineItemAction(random))Usa el aleatorio para invocar un generador:
LineItem line = Arbitraries.of(state.orders()) .flatMap(order -> Arbitraries.of(order.lineItems())) .generator(100).next(random).value();Pero en realidad eso es muy complicado para lo que quieres hacer. Aquí hay una simplificación:
Igual que el anterior, pero no se desvíe con el muestreo:
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);Tanto la opción 1 como la 2 (con suerte) se comportarán razonablemente en el ciclo de vida de jqwik, pero no intentarán ninguna reducción. Por eso recomiendo la siguiente opción.
Para generar la acción:
Arbitraries.integer().between(0, MAX_LINE_ITEMS) .map(cancelIndex -> new CancelLineItemAction(cancelIndex))Úsalo en acción:
List<LineItem> lineItems = state.orders().stream() .flatMap(order -> order.lineItems().stream()) .collect(Collectors.toList()); int randomIndex = cancelIndex % lineItems.size(); LineItem line = lineItems.get(randomIndex);El enfoque se describe con más detalle aquí: https://blog.johanneslink.net/2020/03/11/model-based-testing/
En un futuro más o menos lejano, jqwik puede permitir entregar el estado actual al generar acciones. Esto haría cosas como la tuya un poco más simples. Pero esta característica aún no ha sido priorizada.