Gosh, it isn’t as complicated as it seems. State machine code is very simple and short.
Store the state in a variable, let’s say myState.
You state machine will be a switch statement, branching on the value of the myState variable to exercise the code for each state.
The code will be full of lines like this:
myState = newState;
To enforce state transition requirements, you need to add a little method called instead, like this
void DoSafeStateTransition( int newState )
{
// check myState -. newState is not forbidden
// lots of ways to do this
// perhaps nested switch statement
switch( myState ) {
…
case X: switch( newState )
case A: case B: case Z: HorribleError( newState );
break;
...
}
// check that newState is not undetermined
switch( newState ) {
// all the determined states
case A: case B: case C … case Z: myState = newState; break;
default: HorribleError( newState );
}
}
void HorribleError( int newState )
{ printf("Attempt to go from %d to %d - disallowed\n",
myState, newState );
exit(1);
}
I suggest that this simple and short enough that inspecting will do a better job than unit testing - it will certainly be lots faster!
The point, in my mind, of unit testing is that the test code be simpler than the code tested, so it can be more easily inspected for correctness, then used to test the complicated code. It is often easier to check the state machine code than state machine test code. There is not much point in reporting a 100% unit test pass, when you have little idea if the unit tests are correct.
Put it another way: coding a state machine is easy, designing the correct one is hard. Unit tests will only tell you if you correctly coded the design, not if the design was correct.