I thought I'd weigh in on how I see guards in "stateless" statecharts They're totally deterministic, given the same evaluations of the guards.
When I write tests for my state machines, I set it in an initial state, I send it a sequence of events, and ensure that the guards evaluate to certain values. The state machine simulates timeouts so it runs through all the states almost instantaneously. When it's done, I "expect" a particular side effect to have happened.
So for example, given a simple state machine with states X and Y, and the "foo" event makes it do something:
X foo Y
------> entry / dosomething
exit / stopdoingsometing
I would start the state machine, fire the "foo" event, and then the test would check that the "dosomething" function was called, not that it's in the Y state.
Likewise, if there's a guard, e.g. foo / isReady
— then I could test that the doSomething action is only executed if the test sets up isReady
to be true.
This type of testing / structuring statecharts allows you to refactor the statechart, e.g. introduce substates and so on. For example, Here Y is moved into a substate, and transitions to Y2 on the timeout
element (presumably because an action happens in Y or Y2 (not shown)).
X foo +------- Z ------------------------+
------> | entry / dosomething |
| exit / stopdoingsometing |
| |
| |
| Y ----------> Y2 |
| timeout |
| |
+----------------------------------+
The test still works, and it would even work if the entry/exit actions were moved around from Z to Y, since we don't verify which state the machine entered, simply the side effects of executing the machine.
What the statemachine guarantees (and what I like to test) is that
- Given an uninitialized state machine (start of the state machine)
- And a set of events (or simulated timeouts, e.g. "after 1s")
- And a set of guard evaluations (at each event)
- Then a specific set of side effects will happen (actions)
That was my thinking too - to have "side effects" be declarative as well. Here's the syntax I'm thinking of: