/** * Defines an "autonomous system reaction", meaning the system will react * without needing an event provided via {@link ModelRunner#reactTo(Object)}. * * @param systemReaction * the autonomous system reaction * @return the created system part of this step */ public StepSystemPart<ModelRunner> system(Runnable systemReaction) { Objects.requireNonNull(systemReaction); StepSystemPart<ModelRunner> systemPart = as(systemActor).system(systemReaction); return systemPart; }
/** * Defines an "autonomous system reaction", meaning the system will react * without needing an event provided via {@link ModelRunner#reactTo(Object)}. * Instead, the model runner provides itself as an event to the system reaction. * * @param modelRunnerConsumer * the autonomous system reaction (that needs information from the model runner to work) * @return the created system part of this step */ StepSystemPart<ModelRunner> system(Consumer<ModelRunner> autonomousSystemReaction) { StepSystemPart<ModelRunner> systemPart = user(ModelRunner.class).system(autonomousSystemReaction); return systemPart; }
/** * Makes the model runner continue at the specified step. If there are * alternative flows starting at the specified step, one may be entered if its * condition is enabled. * * @param stepName * name of the step to continue at, in this use case. * @return the use case part this step belongs to, to ease creation of further * flows * @throws NoSuchElementInModel * if no step with the specified stepName is found in the current * use case */ public UseCasePart continuesAt(String stepName) { Objects.requireNonNull(stepName); UseCasePart useCasePart = as(systemActor).continuesAt(stepName); return useCasePart; }
public Model buildWith(ModelBuilder modelBuilder) { normalUser = modelBuilder.actor("Normal User"); anonymousUser = modelBuilder.actor("Anonymous User"); Model model = modelBuilder.useCase("Get greeted") .basicFlow() .step("S1").as(normalUser).system(this::promptsUserToEnterFirstName) .step("S2").as(normalUser).user(ENTERS_FIRST_NAME).system(this::savesFirstName) .step("S3").as(normalUser, anonymousUser).system(this::promptsUserToEnterAge) .step("S4").as(normalUser, anonymousUser).user(ENTERS_AGE).system(this::savesAge) .step("S5").as(normalUser).system(this::greetsUserWithFirstName) .step("S6").as(normalUser, anonymousUser).system(this::greetsUserWithAge) .step("S7").as(normalUser, anonymousUser).system(this::stops) .flow("Handle out-of-bounds age").insteadOf("S5").condition(this::ageIsOutOfBounds) .step("S5a_1").system(this::informsUserAboutOutOfBoundsAge) .step("S5a_2").continuesAt("S3") .flow("Handle non-numerical age").insteadOf("S5") .step("S5b_1").on(NON_NUMERICAL_AGE).system(this::informsUserAboutNonNumericalAge) .step("S5b_2").continuesAt("S3") .flow("Anonymous greeted with age only").insteadOf("S5").condition(this::ageIsOk) .step("S5c_1").as(anonymousUser).continuesAt("S6") .flow("Anonymous does not enter name").insteadOf("S1") .step("S1a_1").as(anonymousUser).continuesAt("S3") .build(); return model; }
@Test public void continuesAtCalledFromFirstStepOfAlternativeFlowWithRightActor() { Model model = modelBuilder .useCase(USE_CASE) .basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(secondActor).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(secondActor, customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_NUMBER).as(secondActor, customer).user(EntersNumber.class).system(displaysEnteredNumber()) .flow(ALTERNATIVE_FLOW).insteadOf(CUSTOMER_ENTERS_TEXT) .step(CONTINUE).as(customer).continuesAt(CUSTOMER_ENTERS_TEXT_AGAIN) .build(); modelRunner.as(customer).run(model).reactTo(entersText(), entersNumber()); assertRecordedStepNames(CONTINUE, CUSTOMER_ENTERS_TEXT_AGAIN, CUSTOMER_ENTERS_NUMBER); }
@Test public void continuesWithoutAlternativeAtNotCalledWhenActorIsWrong() { Model model = modelBuilder .useCase(USE_CASE) .basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(secondActor).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(secondActor, customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_NUMBER).as(secondActor, customer).user(EntersNumber.class).system(displaysEnteredNumber()) .flow(ALTERNATIVE_FLOW).insteadOf(CUSTOMER_ENTERS_TEXT) .step(CONTINUE).as(customer).continuesWithoutAlternativeAt(CUSTOMER_ENTERS_TEXT) .build(); modelRunner.as(secondActor).run(model).reactTo(entersText(), entersText(), entersNumber()); assertRecordedStepNames(CUSTOMER_ENTERS_TEXT, CUSTOMER_ENTERS_TEXT_AGAIN, CUSTOMER_ENTERS_NUMBER); }
@Test public void continuesAfterCalledFromFirstStepOfAlternativeFlowWithRightActor() { Model model = modelBuilder .useCase(USE_CASE) .basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(secondActor).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(secondActor, customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_NUMBER).as(secondActor, customer).user(EntersNumber.class).system(displaysEnteredNumber()) .flow(ALTERNATIVE_FLOW).insteadOf(CUSTOMER_ENTERS_TEXT) .step(CONTINUE).as(customer).continuesAfter(CUSTOMER_ENTERS_TEXT) .build(); modelRunner.as(customer).run(model).reactTo(entersText(), entersNumber()); assertRecordedStepNames(CONTINUE, CUSTOMER_ENTERS_TEXT_AGAIN, CUSTOMER_ENTERS_NUMBER); }
.step("S1").system(promptsUserToEnterName()) .step("S2").user(entersName()).system(greetsUser()) .step("S3").as(firstActor).user(entersName()).system(greetsUser()).reactWhile(someConditionIsFulfilled()) .step("S4").as(firstActor, secondActor).user(decidesToQuit()) .step("S5").as(firstActor, secondActor).system(promptsUserToEnterName()) .step("S6").system(quits()) .flow("Alternative flow A").insteadOf("S4")
/** * Makes the model runner continue after the specified step. * * @param stepName * name of the step to continue after, in this use case. * @return the use case part this step belongs to, to ease creation of further * flows * @throws NoSuchElementInModel * if no step with the specified stepName is found in the current * use case */ public UseCasePart continuesAfter(String stepName) { Objects.requireNonNull(stepName); UseCasePart useCasePart = as(systemActor).continuesAfter(stepName); return useCasePart; }
/** * Makes the model runner continue at the specified step. No alternative flow * starting at the specified step is entered, even if its condition is enabled. * * @param stepName * name of the step to continue at, in this use case. * @return the use case part this step belongs to, to ease creation of further * flows * @throws NoSuchElementInModel * if no step with the specified stepName is found in the current * use case */ public UseCasePart continuesWithoutAlternativeAt(String stepName) { Objects.requireNonNull(stepName); UseCasePart useCasePart = as(systemActor).continuesWithoutAlternativeAt(stepName); return useCasePart; }
/** * Defines which actors (i.e. user groups) can cause the system to react to the * event of this step. * * @param actors * the actors that define the user groups * @return the created as part of this step */ public StepAsPart as(Actor... actors) { Objects.requireNonNull(actors); return new StepAsPart(this, actors); }
StepAsPart(StepPart stepPart, Actor[] actors) { this.stepPart = stepPart; this.step = stepPart.getStep(); step.setActors(actors); connectActorsToThisStep(step, actors); }
@Test public void continuesAtNotCalledWhenActorIsWrong() { Model model = modelBuilder .useCase(USE_CASE) .basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(secondActor).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(secondActor, customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_NUMBER).as(secondActor, customer).user(EntersNumber.class).system(displaysEnteredNumber()) .flow(ALTERNATIVE_FLOW).insteadOf(CUSTOMER_ENTERS_TEXT) .step(CONTINUE).as(customer).continuesAt(CUSTOMER_ENTERS_TEXT_AGAIN) .build(); modelRunner.as(secondActor).run(model).reactTo(entersText(), entersText(), entersNumber()); assertRecordedStepNames(CUSTOMER_ENTERS_TEXT, CUSTOMER_ENTERS_TEXT_AGAIN, CUSTOMER_ENTERS_NUMBER); }
@Test public void continuesWithoutAlternativeAtCalledFromFirstStepOfAlternativeFlowWithRightActor() { Model model = modelBuilder .useCase(USE_CASE) .basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(secondActor, customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_NUMBER).as(secondActor, customer).user(EntersNumber.class).system(displaysEnteredNumber()) .flow(ALTERNATIVE_FLOW).insteadOf(CUSTOMER_ENTERS_TEXT) .step(CONTINUE).as(customer).continuesWithoutAlternativeAt(CUSTOMER_ENTERS_TEXT) .build(); modelRunner.as(customer).run(model).reactTo(entersText(), entersText(), entersNumber()); assertRecordedStepNames(CONTINUE, CUSTOMER_ENTERS_TEXT, CUSTOMER_ENTERS_TEXT_AGAIN, CUSTOMER_ENTERS_NUMBER); }
@Test public void continuesAfterNotCalledWhenActorIsWrong() { Model model = modelBuilder .useCase(USE_CASE) .basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(secondActor).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(secondActor, customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_NUMBER).as(secondActor, customer).user(EntersNumber.class).system(displaysEnteredNumber()) .flow(ALTERNATIVE_FLOW).insteadOf(CUSTOMER_ENTERS_TEXT) .step(CONTINUE).as(customer).continuesAfter(CUSTOMER_ENTERS_TEXT) .build(); modelRunner.as(secondActor).run(model).reactTo(entersText(), entersText(), entersNumber()); assertRecordedStepNames(CUSTOMER_ENTERS_TEXT, CUSTOMER_ENTERS_TEXT_AGAIN, CUSTOMER_ENTERS_NUMBER); }
/** * Defines the type of system event objects or exceptions that this step * handles. Events of this type can cause a system reaction. * * <p> * Given that the step's condition is true, and the actor is right, the system * reacts to objects that are instances of the specified class or instances of * any direct or indirect subclass of the specified class. * * @param eventOrExceptionClass * the class of events the system reacts to in this step * @param <T> * the type of the class * @return the created user part of this step */ public <T> StepUserPart<T> on(Class<T> eventOrExceptionClass) { Objects.requireNonNull(eventOrExceptionClass); StepUserPart<T> userPart = as(systemActor).user(eventOrExceptionClass); return userPart; }
/** * Defines an "autonomous system reaction", meaning the system will react * without needing an event provided via {@link ModelRunner#reactTo(Object)}. * * @param autonomousSystemReaction * the autonomous system reaction * @return the created system part of this step */ public StepSystemPart<ModelRunner> system(Runnable autonomousSystemReaction) { Objects.requireNonNull(autonomousSystemReaction); AutonomousSystemReaction wrappedSystemReaction = new AutonomousSystemReaction(autonomousSystemReaction); StepSystemPart<ModelRunner> systemPart = system(wrappedSystemReaction); return systemPart; }
/** * Defines the type of user command objects that this step accepts. Commands of * this type can cause a system reaction. * * <p> * Given that the step's condition is true, and the actor is right, the system * reacts to objects that are instances of the specified class or instances of * any direct or indirect subclass of the specified class. * * @param eventClass * the class of commands the system reacts to in this step * @param <T> * the type of the class * @return the created user part of this step */ public <T> StepUserPart<T> user(Class<T> eventClass) { Objects.requireNonNull(eventClass); StepUserPart<T> userPart = as(userActor).user(eventClass); return userPart; }
/** * Defines an "autonomous system reaction", meaning the system will react * without needing an event provided via {@link ModelRunner#reactTo(Object)}. * Instead, the model runner provides itself as an event to the system reaction. * * @param modelRunnerConsumer * the autonomous system reaction (that needs information from a model runner to work) * @return the created system part of this step */ public StepSystemPart<ModelRunner> system(Consumer<ModelRunner> modelRunnerConsumer) { Objects.requireNonNull(modelRunnerConsumer); StepSystemPart<ModelRunner> systemPart = as(systemActor).system(modelRunnerConsumer); return systemPart; }
@Test public void actorsCanBeReusedInUseCase() { UseCasePart useCasePart = modelBuilder.useCase(USE_CASE); Model model = useCasePart.basicFlow() .step(CUSTOMER_ENTERS_TEXT).as(customer).user(EntersText.class).system(displaysEnteredText()) .step(CUSTOMER_ENTERS_TEXT_AGAIN).as(customer).user(EntersText.class).system(displaysEnteredText()) .build(); Collection<Step> steps = model.getSteps(); assertEquals(2, steps.size()); Iterator<Step> stepsIt = steps.iterator(); Actor actor1 = stepsIt.next().getActors()[0]; Actor actor2 = stepsIt.next().getActors()[0]; assertTrue(actor1 == actor2); assertEquals(customer, actor1); }