1. Reading

  • Horstmann Core Java, Ed 11, Vol II, Ch 2.7

2. Implementing behavior using state machines.

Systems sometimes have to exhibit some kind of behavior that is different observable output over time. This is different from a pure function, which will always produce the same output for the same input. The behavior differs depending on the 'history' the system experienced, typically in the form of 'events' or method calls that happened over time. Such method calls change the state the system is in. We call such behavior state-full and this way of operating is often implemented using a 'state machine'. In this week and in the AADE course we will be using two concepts:

  1. The state (machine) diagram

  2. The State pattern


lamp battery switch A state machine is a software concept used to implement the behavior of something whose 'reaction' or outcome depends on the events that happened over time. A very simple example is a lamp and a switch. When properly connected and none of the parts is defect, the bulb will light up when the switch is in the on state. As state machine: the switch passes current when it has been turned on, which can be expressed as a turn-on event, making the bulb light up. From the diagram it should be obvious that a current can only flow if the switch is in the on (or closed) state.

In software we model this as the path the code takes, which then depends on some condition or value.

switch The state diagram is a way to express the behavior in a graphical manner. Each state of the system is drawn as a box with rounded corners, like in the diagram on the left. In the GoF State pattern, which is an object oriented way of modeling state behavior, one uses different objects to realize the effect of changing the software path. The objects all implement the same interface, but have a different implementation. Then, whenever a state switching event occurs, the current state object is replaced by the state object for the new state.

railway switch As another analogy, you can think of a railway switch. The active state object will always have the same behavior. One makes the train go straight, the other makes it go right. Each state on itself always does the same, like a pure function, so with the same input (train) the same output is produced. The switching between states makes it possible to control the direction of the train traffic.

State machines or state charts can be implemented in several ways. In this week we will apply an OO approach using the well known State Pattern , combined with enums and some Java 8 features, in particular default methods.

The State Pattern has the following UML class diagram:

StatePattern
Figure 1. State Pattern

We deal with a context, an abstract state and multiple concrete state implementations that do the work and represent the state elements in the diagram. In the diagram above there would be a state instance for both ConcreteStateA and ConcreteStateB. In the implementation of the exercise of this week we will be using enum as the basic construct for our work.


Using a functional state approach by using enum has the following advantages:

  • There is no chance of creating new states. Enum ensures that the only instances there ever will be are created at class loading time. You always know all states there ever will be.

  • A state can be implemented as a pure function, making the number of tests needed minimal.

  • Such functions and thereby state instances can be shared between contexts, because functional states do not have any state themselves. The actual state is saved in the context. This is not because enums cannot have state, they can, but because a functional state in a state-machine shouldn’t. It IS the state and should not have any. It should always do the same. More importantly, they are immutable, meaning no one can break them by setting some wrong value.

  • A functional state is easily tested without a need for a complex context. The context can easily be mocked as can be seen in this weeks exercise.

The disadvantage of using functional states in this way is that they cannot keep information themselves, such as counting something. But quite often the counting involves only a few possible values, which can be states themselves, or can be realized in the context. As an example for few counting states you might consider a traffic light which counts green, yellow, red.

A common error made by novice programmers is to call a method inside a constructor that takes the constructor’s type as a parameter.

    StateMachine(){
      this.initialState = State.IDLE;
      this.initialState.enter( this ); (1)

    }
1 Problematic call of someField.enter(this) inside constructor. Something to avoid.

The problem is that the constructor may not yet be complete, in particular when fields are defined and initialized later in the constructor or even after the constructor definition.

To avoid this problem, you typically have some init() method that should be called after construction or use a factory method that does something similar. In the gumball machine we chose the later. You can find the details in the javadoc documentation of the exercise.


3. Testing

3.1. Soft Assertions

Sometimes you have a pressing need to assert more than one thing in a test method, because two or more values always come in pairs and setting stuff up and dividing it over multiple methods would make the test less readable, which is bad.

However, having multiple asserts (or Mockito.verifies) in one method is bad style, because the first problem that occurs is the only failure that will be reported by the test. You could say that the first failure dominates or monopolizes the test method.

This is where SofAssertions come into play. It can run multiple asserts and report all problems in one fell swoop, because it runs each assert in a kind of sandbox and collects the results (failures) of all at the end.

To see it in action, look at the assert-J doc, in particular soft assertions. I particularly like the lambda style variant called assertSoftly. At the of the lambda block softly object is 'closed', which makes it report any failures that the asserts detected.

Example from Assert-J Doc
    @Test
    void assertSoftly_example() {
      SoftAssertions.assertSoftly( softly -> {
        softly.assertThat( "George Martin" ).as( "great authors" ).isEqualTo( "JRR Tolkien" );
        softly.assertThat( 42 ).as( "response to Everything" ).isGreaterThan( 100 );
        softly.assertThat( "Gandalf" ).isEqualTo( "Sauron" );
        // no need to call assertAll(), assertSoftly does it for us.
      });
    }

3.2. Assumptions

There are test cases that are only useful under certain conditions:

  • The test is only meaningful on a specific Operating System.

  • The test needs a database connection and only executes when available.

  • A file must be present.

  • A 'slow' test flag is set in some file, which only executes the test when that flag is set. Used to avoid slow tests in the normal write-compile-test cycle.

For this you can use Assume.

There are two variants relevant:

You can use the Assumption test in the setup methods (the one with @BeforeXXX), to completely disable all tests in one test class, instead of having each test doing the assumeXXX test. A test that has been canceled because of a failing assumption will appear as a skipped test, similar to a disabled test.

Run on windows only
   assumeThat( System.getProperty( "os.name" ) ).contains( "Windows" );
   // (test) code below this line only executed on Windows.
   generateBlueScreen();

3.3. Testing output

Quite often your program should simply print to System.out. How to verify that what is printed is correct?

There are two approaches.

  1. Pass a PrintStream to the application and have it use that. This is similar to dependency injection you have seen in week 6.

  2. Replace System.out by another PrintStream which can be done with the method System.setOut(PrintStream ps).

In either case you need a PrintStream implementation, whose printed output is easily inspected in a test.

Test printer usage
  PrintStream oldOut;  (1)
  @BeforeEach
  void setupSout(){
       origOut = System.out; (2)
  }

  @Test
  void someTest(){
      StringOutput sout = new StringOutput(); (3)
      PrintStream out = sout.asPrintStream(); (4)
      System.setOut(out);              (5)

    // do interaction

      assertThat( sout.toString() ) (6)
              .contains( expectedText );

  }

  @AfterEach
  void restoreSout(){
      System.setOut( origOut );  (7)
  }
1 Define a field to save old output to be able to restore it.
2 Save it before each test
3 If the test needs to verify the output, use a StringOutput as printStream
4 use it as a PrintStream
5 Replace System.out with this test printstream
6 Do the asserts after the interaction with the SUT
7 Restore the printstream that was saved before the test.

Below you see the implementation of a StringOutput as given in one of the exercises.

StringOutput implementation
public class StringOutput extends OutputStream {

    private final ByteArrayOutputStream byteArrayOuputStream = new ByteArrayOutputStream();

    @Override
    public void write( int b ) throws IOException {
        byteArrayOuputStream.write( b );
    }

    public void clear() {
        byteArrayOuputStream.reset();
    }

    @Override
    public String toString() {
        return new String( byteArrayOuputStream.toByteArray(), StandardCharsets.UTF_8 );
    }
    // Flag to help prevent creation of second PrintStream from this StringOutput.
    private volatile boolean secondPrinter = false;  (1)

    /**
     * Create a print stream that writes to this string output.
     *
     * @return a print stream
     * @throws IllegalStateException on second invocation on this StringOutput.
     */
    public PrintStream asPrintStream() {
        if ( secondPrinter ) {
            throw new IllegalStateException( "cannot create multiple "
                                    + "printstreams for one StringOutput" );
        }
        secondPrinter = true;
        return new PrintStream( this );
    }
}
1 If you are wondering about the meaning of the keyword volatile, it has to do with multiple threads that might want to do the same. Volatile ensures that all threads see that the secondPrinter boolean has been set by the first thread that creates a printer. If you want to read more on the subject, read the explanation on Java Volatile Keyword by Jan Jenkov.
The website containing this page is actually a quite complete tutorial on Java concurrency and multi threading.
We will come back on the subject of multi threaded applications in week 10.

4. State machines and mocking

Testing state machines is a prime example of using mockito. The states are to be tested, and the context in which the state work can be mocked.

5. Slides

6. Exercises

Grade filter with regular Expressions

Reading grades from text source

Use capture groups.

To be able to insert student grades into progress, the teachers want to be able to copy and paste grades from a spreadsheet (which is the typical way grades are assembled) into a text area. In this exercise, a simple file will do as the source of the grades, because working with a real clipboard is a project in itself.

  • The spreadsheet is replaced by a text file with one or more spaces or tabs ("white space") as column separator.

  • The spreadsheet has the typical format that the student number is in the first column and the
    grade for that student is in the last column. The columns in between are arbitrary and may contain anything.

  • You may ignore the spreadsheet part, because the paste action is done in a text area, which can be collected as a text, that can be divided into lines and columns, the column separator being white space.

  • The student number consist of 7 consecutive digits, the grade can consists of one digit,
    two digits (a 10) or two digits separated by a dot or period (.) or a comma ()),
    since both continental Europeans and English speaking teachers want to enter grades this way.

  • The number of columns is not fixed and may change within one input, e.g. having separate column(s) for the optional Dutch 'tussenvoegsel'. A regex pattern to represent this is available in the GradeFilter class.

  • When no grade is found in the input line, and the getResult method is invoked anyway, the result should contain key=<student number> and value =0.0D.

A set of test data is given and has the exact input as the table below.

Table 1. Test data
# "quoted test input" should match found group value found snummer grade

3785895 Jan Jansen 8.7.1997 6.6 8.3 2.4 6,8

true

6,8

3785895

6.8D

3895785 Piet Jansen 8.7.1997

false

3895785

0.0D

3785985 Henk Jansen 8.12.1994 6.6 8.3 2.4 7.9

true

7.9

3785985

7.9D

3785915 Niki Jansen 8.12.1994 6.6 8.3 2.4 8

true

8

3785915

8D

1245717 Joepie Hombergh van den 18.03.1992 10.0 10.0 10.0 10

true

10

1245717

10D

Note that the first column of the csv file is the "quoted input", so should be seen as one input. The rest of the columns are the output values to be used in the assertions. Empty cells represent null values.

The result type of the getResult() method in the GradeCapture class may look a bit strange: Abstract.SimpleEntry<Integer,Double>. The type is what is stored in hash maps and such, and is a way to use a tupple without having to declare the class. It is a kludge, just a way to have a pair of objects. It stems from the fact that the student-number-grade pairs will typically land in a Map of sorts anyway.
Java 14 has a preview feature called record which will make this more way more elegant. There you would write record GradeResult(int snummer,double grade){} as the full 'class' definition, including constructor, getters, toString and hashCode and equals. It is likely to have that as a standard feature in Java 15 and onward, and will be quite useful in case you want a method to return more than just one value.

Your Tasks

  • Complete the TODOs in the test classes. (Press control+6 in NetBeans IDE)

  • In the GradeFilter class complete all TODOs.

  • In the client class there is a last todo and that is to use the grade filter in the getGradesAsMap() method, by reading a file as lines, applying the gradefilter on each line and collect the student numbers and grades in a map with student number as key and grade as value.

There are a few naming problems in the exercise, because of incomplete (renaming) refactoring. Where you see the class name GradeFilter read (or replace it with) GradeCapture.

Test Driven Gumball machine

Please watch Part II of this weeks lecture for a thorough introduction into this exercise! Having a ball with tdd

In this exercise you will test driven develop the behavior of a gumball machine.

You will be implementing a state machine using the GoF State Pattern .

The machine has following events:

  • insertCoin(),

  • ejectCoin(…​),

  • refill(…​),

  • and draw(…​)
    which can be understood as events, modeled in the interface State and should therefore have an implementation in each state.

and four states:

  • SOLD_OUT,

  • NO_COIN,

  • HAS_COIN

  • and WINNER.

State machine
Figure 2. state diagram of the gumball machine.

Study the state diagram, it is the (whole) specification of our gumball system.

You should find that the system starts in the state SOLD_OUT, and will go to NO_COIN after a refill with some gumballs. When you insert a coin, it goes to HAS_COIN, in which you can draw to get a ball. If the machine is empty now, it will go to SOLD_OUT, if not and you are lucky because the winner guard is true, you can draw another ball without extra payment. If not winner or after the extra draw the system will go back to NO_COIN, waiting for the next coin insertion. As last detail, when after WINNER the machine becomes empty because draw takes the last ball, the system will go to the SOLD_OUT out state.

Context
Figure 3. context in the state machine

Because both the Java artefacts Context and State in the initial design are interfaces, we’ve added the UML public visibility modifier to all methods, because that is how interfaces work in Java.

The circled plusses near StateEnum is the UML way of saying that SOLD_OUT etc are inner classes of StateEnum, which they effectively are.

The OlifantysBallMachine implements two interfaces with the exact purpose of

  • GumBallApi being the public API for consumption by the client and the

  • Context interface as one of the collaborators in the state pattern.

To keep the API simple, that interface has one public static method for the client to get an instance of the package private OlifantysBallMachine. This keeps our design nice and tidy with the least possible coupling and leakage of unneeded information to the client. See it as an example of a clean design.

The behavior is easily modeled in a state diagram, but can also be expressed in a state transition table, which has exactly the same information with the added benefit that it can easily be understood by a program, in our case the tests of the state machine. Using such state transition table. This technique predates UML, but is still valuable, in particular because it will list all possibilities and thus will help to find more test cases, ensuring that the set of tests is complete.

gumballmachine thumb In a real machine or automaton, such as a vending machine, the state machine will control one or more devices. As an example, think of a coffee brewer, which must grind coffee, maybe dispense cups, boil water and brew the coffee by pressing the water through the ground coffee, thereby filling the cup. To work properly, these 'devices', such as boiler, grinder, cup dispenser, and pump need to be switched on and off. This is best done on enter (turn on) and exit (turn off) of the state, and is the reason why the State interface provides these methods. They are not used in the gumball exercise because there is no device, only imaginary, but might be used in a future exercise or project. A properly programmed context must therefor call the enter(…​) and exit(…​) methods when changing state, and a context test should verify that.

Test data in a table In the table below, winner and empty are guard expressions, with the outcome specified for the moment of evaluation. Guard expressions have a state decide if it should react to the trigger (guard == true) or not. A guard comes from outside the state and is typically available on the context, as in this exercise. Where the guard is not relevant, it is set to false. Nicer would have been leave the guard’s table cell empty when it is not relevant but that does not play very well with junit csv tests and would complicate the tests. Another approach would be to have a line each for each of the possible guard values, to ensure that guard really does not have effect in specific cases.

For the column End State, empty means no state change.

#Start State trigger End State empty winner dispense refills expected text

HAS_COIN

draw

NO_COIN

FALSE

FALSE

1

0

OlifantysGumball

HAS_COIN

draw

SOLD_OUT

TRUE

TRUE

1

0

OlifantysGumball

HAS_COIN

draw

WINNER

FALSE

TRUE

1

0

OlifantysGumball

HAS_COIN

ejectCoin

NO_COIN

FALSE

FALSE

0

0

Quarter returned

HAS_COIN

insertCoin

FALSE

FALSE

0

0

You should draw to get your ball

HAS_COIN

refill

TRUE

FALSE

0

1

refilled

NO_COIN

draw

TRUE

FALSE

0

0

You must put in a coin before you can continue

NO_COIN

ejectCoin

FALSE

FALSE

0

0

You must put in a coin before you can continue

NO_COIN

insertCoin

HAS_COIN

FALSE

FALSE

0

0

You inserted a coin

NO_COIN

refill

TRUE

FALSE

0

1

refilled

SOLD_OUT

draw

FALSE

FALSE

0

0

Machine is empty

SOLD_OUT

insertCoin

FALSE

FALSE

0

0

Machine is empty

SOLD_OUT

insertCoin

FALSE

FALSE

0

0

Machine is empty

SOLD_OUT

refill

NO_COIN

FALSE

FALSE

0

1

refilled

WINNER

draw

NO_COIN

FALSE

FALSE

1

0

You got two gumballs for your coin

WINNER

draw

SOLD_OUT

TRUE

TRUE

1

0

OlifantysGumball

WINNER

insertCoin

FALSE

FALSE

0

0

You should draw once more to get an extra ball

WINNER

insertCoin

FALSE

FALSE

0

0

You should draw once more to get an extra ball

WINNER

refill

FALSE

FALSE

0

1

refilled

# STATE

trigger pairs

that

have

not been

used

yet

and should be ignored

WINNER

ejectCoin

FALSE

FALSE

0

0

You should draw once more to get an extra ball

SOLD_OUT

ejectCoin

FALSE

FALSE

0

0

Machine is empty

We will start testing the state enum StateEnum.

The table reiterates that the state HAS_COIN needs the most attention in testing.
Also note that this table is just the specification, just like the state diagram, and not the implementation.

To get our State tested without any real machine nearby, we must mock out Context. This can be done by hand, but would soon make us create a context implementation that sort of works, but

  • first of all, is not tested itself, thus violates the TDD workflow

  • does not behave the way we need it in the test for the State type

  • and we do not need to have this context to behave in any way other then to give predictable answers when called by the State, and we must ensure that the State has the exact interactions we want. Not too few, and also not too many.

This is where Mockito comes in. We only use a few of its features, namely

  • the mocking facility,

  • recording and playback of method calls and return values

  • verification of the correct calls on the mocked Context.

Our setup looks as follows:

preparing for tests: setup.
    final Context ctx; (1)

    StringOutput sout = new StringOutput(); (2)
    PrintStream out = sout.asPrintStream(); (3)

    /**
     * Map the trigger name from the csv file to a lambda expression of type
     * {@code BiConsumer<Context,State>}.
     */
    static Map<String, BiConsumer<Context, State>> triggerMap = Map.of( (4)
            "insertCoin", ( c, s ) -> s.insertCoin( c ),
            "ejectCoin", ( c, s ) -> s.ejectCoin( c ),
            "drawBall", ( c, s ) -> s.draw( c ),
            "refill", ( c, s ) -> s.refill( c, 5 )
    );

    public CsvFileSourceStateTest() { (5)
        ctx = Mockito.mock( Context.class );
        when( ctx.getOutput() ).thenReturn( out );
        // any colour will do.
        when( ctx.dispense() ).thenReturn( new OlifantysGumball( "RED" ) );
    }
1 The context is mocked in the constructor
2 StringOuput and PrintStream are combined into a PrintStream that takes the role of system output.
3 Creates a StringOutput and ensure that anything output by the context is returned in an easily interpreted String.
4 translates strings to actual lambda expressions that do the interaction in the test.
5 Is the constructor that creates a mock for the context and teaches it to return the appropriate things on specific method calls.

This basic setup is done for every test anew and makes sure our SUT has a fresh collaborator every time, ignorant of what happened in earlier tests.

First test

The tabular form of the state machine behavior above is taken as the input form the parameterized tests.

First we get the initial state from the csv test source, which is in the column name "Start State". Because we need a State enum value we need to look that up using the enum classes' values() method.

When a state method is invoked, it can or should have multiple interactions with the context. To test that, we use the mocked context. First we need to complete it’s teaching, that is what to answer on the boolean methods isEmpty() and isWinner(). We take the values from the two columns winner and empty

seq draw
Figure 4. sequence diagram of draw

Here is an example a first test: Make sure the machine does not return any ball(s) when it’s state is NO_COIN. From the State diagram we learn that the action draw() should only be effective in the state HAS_COIN. This gives the interaction, modeled in the sequence diagram below. The dispense method should only be called in specif states.

As can be seen in the sequence diagram, the dispense() call is only allowed in the state HAS_COIN, not in NO_COIN. Let the test verify that using a mockito verify. A Mockito verify is similar to a JUnit assertXXX, it will complain (in Java terms: throw an Assert Exception), that a dispense() is NOT happening.

first test
    @ParameterizedTest
    @CsvFileSource( resources = { "testtable.csv" } )
    public void verifyDispense(
            int nr, String initialStateName, String finalStateName,
            String triggerName, boolean empty, boolean winner,
            int dispenseCount, int addBallsCount, String expectedText ) {

        State initialState = StateEnum.valueOf( initialStateName ); (1)
        var triggerAction = triggerMap.get( triggerName );          (2)

        // prime collaborator
        when( ctx.isEmpty() ).thenReturn( empty );                  (3)
        when( ctx.isWinner() ).thenReturn( winner );                (4)

        triggerAction.accept( ctx, initialState );                  (5)

        verify( ctx, times( dispenseCount ) ).dispense();           (6)
    }
1 Select the appropriate instance of the State.
2 Lookup the interaction in the 'lambda' map, which translates strings to BiConsumer of Context and State.
3 Read the values empty and
4 winner from the table and use them to train the mocked context.
5 Do the interaction
6 verify the the number of dispenses matches the requirement, in the NO_COIN state 0.

CsvFileSourceStateTest has in total 4 tests which assert all stated requirements in the table.

assertMessage that verifies that the correct output is 'printed'
verifyDispense ensures that the exact number of balls is dispensed, 0 or 1.
verifyRefillAddsBalls that some balls are added to the machine when refill is called
verifyStateTransition does what the name says.

Do not forget the context.

The context’s implementation becomes surprisingly simple. It only needs to forward the event or 'trigger' to the current state, which in turn will tell the context what to do.

The context must provide a bit of functionality to make the actions directed by the states meaningful.

  • forward the trigger to the state, providing itself as the first parameter. For instance insertCoin() in the API should result in an `insertCoin(Context) call in the state.

  • Remember what state the context is 'in'. We do that with the changeState method.

  • invoke the exit and enter method when we swicth between states.

  • invoke the enter method on the initial state.

and a few more. These test bodies are given in the test class 'OlifantysMachineImpleTest' with their purpose, so you should be able to fill in the blanks.

How about coverage
Some of the tests in OlifantysMachineImpleTest are there for the purpose of maintaining a high coverage. Since you are already familiar with test coverage, it simply is a mater of turning on the proper maven profile.

The csv test data are in a separate resource folder. If you try to test just one file, make sure you do test project first, which makes maven copy over the csv file to a place where the unit test can find it.


6.1. Test run reports Week 8