itemis CREATE supports you with testing of statecharts and generated state machines. Learn more about test-driven statechart modelling and how to use SCTUnit to develop your statechart model step by step using a test-driven approach.
In test-driven development, before writing any code the developer creates a unit test that checks the to-be-established implementation for correctness. Only after that, the developer codes the implementation and executes the test. If the test fails, the developer has to fix the implementation – perhaps repeatedly – until the test succeeds.
Tests are not written for the complete software product in advance, but rather on a per-feature basis. That is, the developer writes tests only for the one single feature he is going to implement next. Having successfully finished that implementation, the developer should refactor his code and his tests. The most recent additions might have opened opportunities to restructure one or the other, thus reducing complexity and/or adding readability. Refactoring must never change the semantics of the code. To ensure that, tests come in handy and should be run after each refactoring step.
In model-driven software development (MDSD), specific parts of the software are created by a code generator instead of being written manually. This also pertains to code generated from statechart models.
The nice thing about generated code is that we can safely assume it to be correct – at least as long as the code generator developers have tested their product thoroughly enough. However, what about the model? While the generated code might be correct, this won’t help at all if the underlying model is broken in the first place.
SCTUnit is a scripting and testing framework for writing unit tests for statechart models. You can validate the behaviour of your statechart by running these tests. Each test, written in the SCTUnit language, consists of a well-defined sequence of instructions. When running a test, these instructions are applied to a state machine under control and possibly changes that state machine in a specific way. You put down the expected effects of these changes as assertions. An assertion is used to check whether a specific condition is fulfilled, for example certain states being active or not, variables having specific values, operations having been executed, etc. SCTUnit allows test-driven development of statechart models on the semantic level.
The subsequent sections will first introduce SCTUnit by example and then explain the SCTUnit language in full.
This section introduces SCTUnit by a simple statechart example that models a light switch. We will develop this statechart step by step using a test-driven approach. That is, for each single development step, we will first write a test, then change our statechart model, then run all the tests we created so far until they succeed. After that, we will proceed with the next development step.
Before implementing any tests or models, let’s write down the requirements for our light switch statechart model:
Now we need an Eclipse project to host our statechart model. So let’s create one, naming it light_switch. Within the project, we create two folders, model and test, for – you guessed it – the model and the tests, respectively.
In the test directory, proceed as follows to create a SCTUnit test file:
As a result, the empty file light_switch.sctunit is created in the test folder and is opened in an editor.
In the spirit of test-driven development, create a test for the light switch model’s initial behaviour by writing the following in the light_switch.sctunit file:
testclass light_switch_tests for statechart light_switch {
@Test
operation isStateOffActiveAfterInit () {
enter
assert active (light_switch.main_region.Off)
}
}
Let’s walk through this example line by line and see what all of this means.
testclass light_switch_tests for statechart light_switch {
testclass introduces a test class, a collection of tests. In this example, it is named
light_switch_tests.
for statechart are followed by the name of the statechart, here:
light_switch. However, since there is no
light_switch statechart yet, an error marker is attached to the statechart name, as shown in the following screenshot:
{ and
}.
@Testoperation isStateOffActiveAfterInit () {
@Test. This example defines the operation
isStateOffActiveAfterInit. It has no parameters, which is indicated by (). As we will see later, an operation does not necessarily have to be a test, but can be a mere subroutine. Such operations can have parameters. The operation’s body is bracketed in braces.
enter
assert active (light_switch.main_region.Off)
Since the test has error markers, we can say that it fails even without being run. So let’s fix that with minimal effort and create a model that is called
light_switch and has a state
Off. Also, change the annotation
EventDriven to
CycleBased(200). Figure
"First step of creating a light switch model" shows the resulting model.
First step of creating a light switch model
Figure "First step of creating a light switch model" also shows that the error markers of the test class have been cleared, because our minimal statechart model has the name that is specified in the test and it also contains the Off state.
Now we can execute the test class. In order to do so, proceed as follows:
The model by now implements the first requirement specified in section "Light switch requirements".
So let’s take care of the second requirement, formulate it as a SCTUnit test, and add it to the test class.
The whole test class now looks like this:
testclass light_switch_tests for statechart light_switch {
@Test
operation isStateOffActiveAfterInit () {
enter
assert active (light_switch.main_region.Off)
}
@Test
operation isStateOnActiveAfterOperateInOffState () {
enter
raise operate
proceed 1 cycle
assert active (light_switch.main_region.On)
}
}
The
raise operate instruction raises the
operate event. However, aside from this event having been raised, nothing will happen yet. The next instruction will change that.
The
proceed 1 cycle instruction orders the state machine to execute one run-to-completion step (RTC). During this RTC, the state machine can react to the raised
operate event.
Processing the
operate event should cause the
On state to become active. This is checked by the following assertion:
assert active (light_switch.main_region.On)
To make the test class syntactically correct, the statechart model must be amended by the operate event and by the On state.
However, in order to demonstrate what happens if an SCTUnit test fails, let’s have a look at a wrong implementation, as shown in figure "Erroneous light switch model". Here the operate event does not trigger a transition from Off to On but from Off back to Off.
Erroneous light switch model
The test operation isStateOnActiveAfterOperateInOffState should detect this semantic error and alert us. Running the amended test class as an SCTUnit again indeed leads to the following result:
The large bar is red now instead of green, meaning that at least one test failed. Below the bar the test class and its tests are listed and it is indicated which tests succeeded and which tests failed. We can see that our isStateOffActiveAfterInit test still succeeds; so while we didn’t correctly implement the second requirement, we at least didn’t break the first one.
Fixing the model as follows turns the test green:
Okay, so let’s write a test for the third and final requirement:
@Test
operation isStateOffActiveAfterOperateInOnState () {
isStateOnActiveAfterOperateInOffState
raise operate
proceed 1 cycle
assert active (light_switch.main_region.Off)
}
A prerequisite for dealing with an operate event in the On state is to get into that state in the first place. However, we have implemented the necessary actions before, namely in the test isStateOnActiveAfterOperateInOffState. The great thing is that we can just use that test by calling it as subroutine. This is like calling a function or method in any other programming language.
We know that after executing isStateOnActiveAfterOperateInOffState the On state is active – we even assert that in the called test operation. So now we just have to raise operate again, cycle the statechart once and check whether we are in the Off state after that.
By going from Off to On and back to Off, we have ensured now that the light switch model behaves as specified. However, we might want to check whether this is still the case if we go through this cycle a number of times, say twice or 20,000 times. To support scenarios like this, the SCTUnit language provides a couple of advanced features, see section The SCTUnit language. Here we use the while loop to iterate over the off-on-off cycle a couple of times:
@Test
operation isStateOffActiveAfter10Cycles () {
enter
var i: integer = 0
while (i < 10) {
raise operate
proceed 1 cycle
assert active (light_switch.main_region.On)
raise operate
proceed 1 cycle
assert active (light_switch.main_region.Off)
i = i + 1
}
}
Test coverage is a metric that describes to which extent your code is covered by your tests. Translated to statechart modelling, a test coverage describes which parts of your state machine were activated during test execution. This information allows to identify missing tests and thereby to increase test quality.
Whenever a test set is executed, a test coverage is computed on the fly. The test coverage metrics can be examined in the Coverage View. The coverage value is given in percent for each model element and is defined as follows:
In the following example you can see that state On has a coverage of 50%. Although it is entered by the tests, its outgoing transition On -> Off is not covered.
The coverage view contains the following toolbar buttons:
: Shows / hides fully covered elements in the tree
: Clears the view and removes the coverage highlighting in the model
The coverage highlighting feature transports the coverage values directly onto the statechart model by coloring all elements in the colors:
You can enable the highlighting by selecting an arbitrary element in the coverage view.
With the latest 5.2.0 update, we’ve introduced a useful feature: coverage files that are now generated and saved on your computer. These have the extension “.cov” and can be saved alongside the corresponding SCTUnit file in your workspace. This allows you to easily compare your current coverage results with past ones to see how test coverage is improving over time.
We’ve also made it convenient for you to set your SCTUnit Coverage preferences. Just head over to the SCTUnit “Run Configurations” and look for the new “Coverage” tab right next to the “Common” tab. This lets you adjust coverage settings according to your preferences.
Switching between SCTUnit Editors will now automatically load the most recent previously calculated coverage. This avoids the need to recalculate any coverage information.
Also, it is now possible to export a HTMLreport of your test coverage. To do this, click the save icon once your coverage has been calculated.
A dialog will open allowing you to save the .zip file in the location of your choice. The HTML file contained can then be opened in your browser to view the report. The report contains all the information about the coverage of your tests as well as a highlighted image of each statechart covered by your SCTUnit tests.
While section "SCTUnit by example" gave an overview of test classes and operation, the section at hand provides you with a complete description of the SCTUnit language. Since the SCTUnit language is an extension of the statechart language, this section will focus on language elements that are specific to the SCTUnit language. Please see section "The statechart language" for everything else.
An important extension of the SCTUnit language over the statechart language is that you can write your own operations (subroutines) and control statements, like if and while . They make it possible to “script” complex procedures, for example by raising different events under different conditions or by looping over a sequence of statements multiple times.
Please remember that you can always hit [Ctrl]+[Space] when editing SCTUnit language files within itemis CREATE. The editor will show you all possible input choices that are valid at your cursor location.
A test class contains one or more operations or test cases. It is contained in a file with a .sctunit filename extension.
Example:
testclass NameOfTheTestClass for statechart NameOfTheStatechart {
const pi : real = 3.141592654
var sum : integer = 0
@Test
operation isFeatureAvailable () {
/* … */
}
}
The
header of a test class consists of the keyword
testclass, followed by the name of the test class, followed by the keywords
for statechart, followed by the name of the statechart this test class relates to.
The test class imports the statechart, so all variables, operations, events, etc. that are defined in an interface in the statechart’s definition section, as well as the statechart’s states, are available in and accessible by the test class.
Please note: Entities defined in a statechart’s internal scope are not visible from the outside, including test classes controlling the statechart.
The
body of a test class is enclosed in braces ({ … }). It consists of an optional part with variable and constant definitions, followed by at least one
operation.
Figure "Test class grammar" summarizes the structure of a test class:
Test class grammar
Test classes are namespace-aware: Using the package statement , you can insert a test class into a specific namespace.
Test classes can be grouped into a test suite.
Operations are comparable to methods, functions, or subroutines in other programming languages. Operations are defined in test classes.
A
test is parameterless operation without a return type that is annotated with @Test.
Example:
@Test
operation isFeatureAvailable () {
enter
var i: integer = 0
var countValue: integer = count
while (i < 10) {
raise do_cycle
assert active (myStatechart.main_region.State_A)
assert count == countValue + 1
i = i + 1
}
}
The
header of an operation consists of the keyword
operation, followed by the name of the operation, followed by a parenthesized list of parameters, optionally followed by a colon and a return type. If no return type is specified, it is inferred from the operation’s
return statement(s)
.
Operations can also be annotated. Operations annotated with
Test are automatically executed when the SCTUnit test is executed.
The
body of an operation is enclosed in braces ({ … }). It consists of a sequence of
statements, which may be empty. If the operation’s header specifies a return type different from
void, the operation must return a value of that type, using the
return statement
.
Figure "Operation grammar" summarizes the structure of an operation:
Operation grammar
An operation can define its own variables. Aside from these
it also has access to
An operation can call other operations. This is like subroutine calls in other programming languages.
Operations that are declared in the statechart cannot be called from a test class.
When running a
test class or a
test suite as an SCTUnit, only those operations are executed as tests that are annotated with @Test. Operations annotated with @Ignore or not annotated at all will not be regarded as tests. However, they can be called by test operations.
While the @Ignore annotation and an omitted annotation have the same effect functionally, you should use the @Ignore annotation to mark operations that are intended as tests, but are (temporarily) disabled for one or the other reason. This differentiates them from other operations that are not tests, but mere subroutines called from elsewhere.
The body of an operation consists of statements. Many types of statements, like variable definitions, assignments, event raisings, or operation calls, are already defined in the statechart language and are described in section "Statements" of the statechart language documentation.
Statement types that are specific to the SCTUnit language are described in the following subsections.
The return statement terminates the execution of the current operation. It either returns nothing or the value of an expression to the caller. If an expression is returned, its type must match the operation’s return type. If the operation returns nothing, its return type must be void, either implicitly or explicitly.
Example:
operation getCircleArea (radius: real): real {
const pi: real = 3.141592654
return pi * pi * radius
}
The
return statement evaluates the expression
pi * pi * radius, i.e., the area of a circle with radius
radius, and returns the result to the caller of the operation. .
Figure "Return statement grammar" summarizes the structure of the return statement:
Return statement grammar
active is a built-in function of the statechart language. In the context of testing, it can be used to determine a state’s state at a given time. It returns a logic value, if the state in question is active it returns true, otherwise false.
Example:
assert active (myStatechart.main_region.State_A)
The first statement asserts that state State_A in region main_region in statechart myStatechart is active. If this is not the case, the test fails. You can read about assertion in the next chapter.
When it comes to testing, the most important statement is the assertion. It evaluates a condition that must be fulfilled for the test to not fail.
Example 1:
assert sum == 42
This statement asserts that the variable sum has a value of 42. If this is the case, the operation continues. If not, the test fails and is stopped.
Unlike some other test frameworks, SCTUnit does not differentiate between
assertandassert fatalor similar. All assertions are “fatal”, which means they stop the test when they fail.
Example 2:
assert active (myStatechart.main_region.State_A)
assert !active (myStatechart.main_region.State_B) message "State_B must not be active here."
The first statement asserts that state State_A in region main_region in statechart myStatechart is active. If this is not the case, the test fails.
The second statement asserts that State_B is not active. Otherwise the test fails with the error message “State_B must not be active here.”.
Please note: active(…) is a built-in function of the statechart language.
Generally, an assertion consists of the keyword
assert, followed by a boolean expression, optionally followed by the keyword
message and an error message text (string literal). The assertion expects the boolean expression to be
true to continue the test. If it evaluates to
false the test fails. The optional message can be used to clarify what went wrong.
A special assertion variant uses the
called keyword ("
assert called statement"). It checks whether a certain operation has been called (executed), typically by some action in the statechart.
Example:
assert called myOperation
assert called myOperation(42, 815)
assert called myOperation 4 times
assert called myOperation(42, 815) 1 times
assert ! called myError
The first assertion checks whether the operation myOperation has been called during the execution of this test. If it hasn’t, the test fails.
The second assertion not only checks whether the operation
myOperation has been called, but also checks whether it has been called with parameters 42 and 815. If the operation hasn’t been called at all, the test fails. The test also fails if the operation has been called, but with different parameters than 42 and 815, e.g.,
myOperation(1, 2).
The third assertion checks whether the operation myOperation has been called at least 4 times, no matter the arguments, while the fourth assertion checks if the operation has been called at least one time with the specified parameters.
The fifth assertion checks if the “forbidden” operation
myError has been called, and fails in that case.
Finally, you can use the
assert statement to check whether the state machine has raised an outgoing event. To do so, the
assert keyword is followed by the name of the desired event. It is also possible to assert the opposite, i.e., that the event has
not been raised. In this case, insert the negation operator
! between
assert and the event name.
Here’s an example:
The statechart below transitions from state A to state B on either the e1 or the e2 incoming event. However, on e1, the outgoing event e3 will be raised, while this is not the case on e2.
Statechart raising an outgoing event
You can verify this behavior using the following SCTUnit test class. The statement
assert e3
succeeds if the e3 event has been raised, while
assert ! e3
succeeds if that event has not been raised.
testclass outgoingEventTest for statechart raiseOutgoingEvent {
@Test
operation test_e1 () {
enter
raise e1
proceed 1 cycle
assert e3
exit
}
@Test
operation test_e2 () {
enter
raise e2
proceed 1 cycle
assert ! e3
exit
}
}
Figure "Assertion grammar" summarizes the structure of an assertion:
Assertion grammar
The enter statement serves to enter the statechart associated with this test class. The state machine is initialized and started. The state that is denoted by the initial state becomes active.
A test must execute the enter statement before it can perform any sensible testing on the statechart. Unless the state machine is entered, all states are inactive.
Please see section "SCTUnit by example" for examples on how the enter statement is used. Please also see the section on the exit statement .
The exit statement exits and quits a state machine. You can re-initialize and re-enter it using the enter statement .
Example:
The statechart myStatechart looks like this:
Statechart myStatechart
The enter and exit statements are explained by the comments of this test class:
testclass enter_exit_tests for statechart myStatechart {
@Test
operation checkState () {
/* Before entering the state machine, all states are inactive: */
assert !active (myStatechart.main_region.State_A)
/* Now we are entering the state machine. The state that the
* initial state points to becomes active: */
enter
assert active (myStatechart.main_region.State_A)
/* The "exit" statement leaves the state machine. All of the
* latter's states are thus inactive:
*/
exit
assert !active (myStatechart.main_region.State_A)
/* It is possible to re-enter the state machine or to be precise:
* to enter a new instance of the state machine. As above,
* "State_A" should be active now: */
enter
assert active (myStatechart.main_region.State_A)
}
}
The raise statement raises one of the state machine’s incoming events.
Example 1:
raise operate
This statement raises the operate event, defined in the state machine’s default interface.
Example 2:
raise valueChanged : 3
This statement raises the valueChanged event, defined in the state machine’s default interface, which is of type integer. Raising a typed event without a payload is not allowed.
Example 3:
raise user.click
This statement raises the click event, defined in the state machine’s user interface.
Since state machine internals are inaccessible to SCTUnit tests and outgoing events cannot be raised in general, only incoming events can be raised. Internal events, defined in the internal scope, and outgoing events, defined in interfaces, cannot be raised.
Please note: The raise statement is not specific to the SCTUnit language, but is (also) part of the statechart language, see section "Raising an event".
The proceed statement allows to proceed the state machine either in terms of time or in terms of run-to-completion steps (RTC). Cycle-based state machines can be explicitly told to perform an RTC step, while event-driven state machines run an RTC step implicitly when an incoming event is raised.
The
proceed statement consists of the keyword
proceed and an indication by what to proceed. Two variants are available:
proceed
number
time_unit – This variant instructs the state machine to proceed by the specified time, e.g.,
proceed 30 s proceeds by 30 seconds. For cycle-based statecharts, RTC steps will be performed based on the defined cycle time. For example,
proceed 400 ms on a statechart with
@CycleBased(200) annotation, two RTC steps will be performed. Supported time units are:
s – seconds
ms – milliseconds
us – microseconds
ns – nanoseconds
proceed
number
cycle – This variant is only available for cycle-based statemachines and instructs the state machine to perform
number run-to-completion steps. Proceeding cycles also implies proceeding of time based on the cycle period defined by the
@CycleBased() annotation.
Example for a machine with @CycleBased(200) annotation:
raise operate
proceed 100 ms
raise user.click
proceed 1 cycle
The statement
proceed 100 ms will proceed the time accordingly, however, as the cycle period is set to 200 ms, no RTC step is performed. Hence, the event
operate is not yet handled. The subsequent statement
proceed 1 cycle will proceed the time by exactly 100 ms so that an RTC step is executed. In this RTC step, both events are active simultaneously. For an event-driven machine the
proceed 1 cycle statement is not available. An RTC step is performed for each
raise statement instead.
When running an SCTUnit test, the state machine does not run in real time, but in
virtual time instead. That is, a statement like
proceed 3600 s does not have to wait for one hour of real time to elapse. Instead the state machine “leaps” by one hour in an instant, raises all affected time events, and processes them.
Figure "Proceed statement grammar" summarizes the structure of the proceed statement:
Proceed statement grammar
Variables and constants (for brevity we’ll summarize both as “variables”) can be defined as specified in the statechart language, please see sections "Variables" and "Constants" for all the details.
However, variables in the SCTUnit language must always be initialized. For example, while a definition like
var sum: integer
is fine in the statechart language, it is an error in the SCTUnit language. You would rather have to write something like
var sum: integer = 0
Variables can be defined in the scope of the test class or in operations.
The if statement executes a sequence of statements depending on a condition.
Example 1:
if (i < 5) {
raise do_cycle
}
The do_cycle event is raised if the variable i has a value that is less than 5. Otherwise nothing happens.
Example 2:
if (i < 5) {
raise do_cycle
} else {
raise button5
}
The do_cycle event is raised if the variable i has a value that is less than 5. If i is equal to or greater than 5 the button5 event is raised instead.
The
if statement starts with the keyword
if, followed by a boolean expression in parenthesis, followed by a sequence of statements in braces. The sequence of statements must contain at least one statement. It is executed if and only if the boolean expression evaluates to
true.
And optional
else clause may follow. It consists of the keyword
else, followed by a sequence of statements in braces. The sequence of statements must contain at least one statement. It is executed if and only if the boolean expression evaluates to
false.
Figure "If statement grammar" summarizes the structure of the if statement:
If statement grammar
The while statement executes a sequence of statements repeatedly, as long as a condition is fulfilled.
Example:
var i : integer = 0
while (i < 10) {
raise do_cycle
proceed 1 cycle
i = i + 1
}
The statements in the while loop’s body are executed ten times.
The
while statement starts with the keyword
while, followed by a boolean expression in parenthesis, followed by a sequence of statements in braces, the loop’s body. The loop’s body must contain at least one statement. It is executed repeatedly if and only if the boolean expression evaluates to
true. The expression is evaluated before the first execution of the loop body and after each execution of the loop body.
Figure "While statement grammar" summarizes the structure of the while statement:
While statement grammar
The keywords
active,
is_active and
is_final make it possible to retrieve certain aspects of the state machine’s status as boolean values and e.g., use them in
assertions.
Example:
assert is_active
The assertion succeeds if at least one state is active. This is always the case if the state machine has been entered and has not been exited. Please note that a final state can be active, too.
The assertion fails if the state machine has not been entered or has been exited.
Example:
assert is_final
The assertion succeeds if the state machine is active (see above) and all its active states are final states.
The assertion fails if the state machine is not active (see above) or it has at least one active state that is not a final state.
Example:
assert active(main_region.StateA)
The assertion succeeds if the specified state is active. States have to be fully qualified with their containing region etc., because state names are not unique in a statechart.
The assertion fails if the specified state is not active.
The mock statement allows you to mock operations defined in the statechart. You can specify what should be returned when the operation is called, even depending on the given input parameters.
Consider a complex operation getNeutronFlux, which takes a real value as an argument and returns a real value as a result. During semantic unit testing of your statechart – as opposed to integration testing –, you won’t want to integrate the operation into your testing environment. Aside from that, it’s currently not possible to call operations from SCTUnit, for example, operations defined in a Java class.
Your statechart, however, depends on getNeutronFlux returning actual results, as can be seen in the simple model shown in figure "Neutron flux statechart":
Neutron flux statechart
While State_A is active, the state machine will call getNeutronFlux(p) during each run-to-completion step and, depending on the results, will take the transition to State_B (or not).
However, how could you test the behaviour of your statechart if you cannot call an actual operation and retrieve its results?
That’s what the mock statement is for. It is a makeshift that mimics an actual operation call by two mechanisms:
A test can create an arbitrary number of such mappings, i.e., it can use the mock statement multiple times for multiple operations, or for multiple parameter list of the same operation.
Consider the following test:
@Test
operation testNeutronFlux() {
mock getNeutronFlux(12.0) returns (1000000000.0)
mock getNeutronFlux(18.0) returns (4.3)
enter
p = 18.0
proceed 1 cycle
assert active(neutronFlux.main_region.State_A)
p = 12.0
proceed 1 cycle
assert active(neutronFlux.main_region.State_B)
}
The effect of the two mock statements is as follows:
The test assigns 18.0 to the statechart variable
p, and performs one run-to-completion step. During this RTC, the state machine calls
getNeutronFlux(p) in order to evaluate the guard condition
[getNeutronFlux(p) >= 100.0]. Since
p has a value of 18.0, the mocked operation returns 4.3, and the transition from
State_A to
State_B is not taken.
After that, the test sets p to 12.0 and executes another RTC. This time, the mocked operation returns 1000000000.0, the guard condition evaluates to true and the state machine transitions to State_B.
Calling getNeutronFlux with any other parameter value than 12.0 or 18.0 would not only let the test fail, but would also throw an exception, because the actual operation cannot be called in test mode (simulation mode) and its return value is undefined.
In order to avoid an exception to be thrown, you can define a mock statement with a default return value. This value will be returned from all calls of the mocked operation that are not explicitly overridden by mock statements with specific parameters.
The mock statements in the example above might have better been written as follows:
mock getNeutronFlux returns (-1.0)
mock getNeutronFlux(12.0) returns (1000000000.0)
mock getNeutronFlux(18.0) returns (4.3)
The first mock statements defines a return value of -1.0 for each and every call to the getNeutronFlux operation, irrespective of the parameter value. The following statements, however, override this setting for the parameter values 12.0 and 18.0.
Please note: The order of the mock statements is important! You should define the general case first, followed by specifying return values for specific parameter lists.
The type of the default value specified in the mock statement must match the return type of the mocked operation.
Figure "Mock statement grammar" summarizes the structure of the mock statement:
Mock statement grammar
A
test suite aggregates a set of
test classes into a logical unit. It is contained in a file with a
.sctunit filename extension.
Example:
testsuite MyTestSuite {
TestClassA,
TestClassB,
TestClassC
}
The test suite MyTest_Suite comprises the test classes TestClassA, TestClassB, and TestClassC.
The nice thing about test suites is that you can run all tests of all the test classes at once. Right-click on the test suite file, say, mytestsuite.sctunit, and select Run As → SCTUnit in the context menu. All tests in all test classes referenced in the test suite will be executed.
You can put test classes that are testing different statecharts into a single test suite. However, all test classes within a test suite must pertain to statecharts using the same language domain. For example, if you have a statechart using itemis CREATE' default domain and another statechart using the C domain, you cannot put their respective test classes into the same test suite. Instead, you would have to write two different test suites: one for the test classes testing your “normal” statecharts, the another one for testing your “C” statecharts.
The
header of a test suite consists of the keyword
testsuite, followed by the name of the test suite.
The
body of a test suite is enclosed in braces ({ … }). It consists of one or more names of test classes, separated by comma.
Figure "Test suite grammar" summarizes the structure of a test suite:
Test suite grammar
Test suites are namespace-aware: Using the package statement , you can insert a test suite into a specific namespace.
You can organise your test classes and test suites in different namespaces. Each test class or test suite can assign itself to a namespace by the package statement.
Use the package statement to determine a namespace for the test class or test suite in the current .sctunit file. The package statement is optional, but if you use it, it must be the first statement of your .sctunit file. Test classes and test suites without a preceeding package statement will be put into the default namespace.
Example:
package foo.light_switch.test
testclass light_switch_tests for statechart light_switch {
…
}
The package statement puts the light_switch_tests test class into the foo.light_switch.test namespace.
The
package statement consists of the keyword
package, followed by the package name (namespace). The package name is fully-qualified, i.e., in dot notation.
Please see section "Test units" for a summary of the package statement’s and related statements' grammar.
A test unit is either a test class or a test suite. It is contained in a file with a .sctunit filename extension.
Executing SCTUnit tests within the Eclipse development environment as described in section "Running the test" is a nice and handy feature for the developers. However, it all happens on the modeling level, a higher abstraction level than the actual source code you are likely generating from your statechart. You will probably want to test the generated source code as well, especially in any safety critical environments.
Generating unit tests as source code comes as a solution. Source code can be inspected, compiled, and integrated wherever needed.
This section explains how you can generate your SCTUnit tests as source code with itemis CREATE. Supported programming languages are C, C++, Java, Python as well as SCXML for Qt. The SCTUnit code generators translate the SCTUnit tests into a unit test framework of the target language. For example, the generated Java test code uses JUnit as its testing framework and Mockito for mocked methods. C and C++ sources rest upon the Google Test unit testing library.
In order to use the SCTUnit code generators effectively, you should already be familiar with generating source code from statecharts, as explained in chapter "Generating state machine code". In fact, you have to generate your statechart as source code first, so that the SCTUnit tests have something to be executed upon. Below we are assuming that you have your statechart code generator model and the generated code handy. Generating SCTUnit test source code follows the same principles as generating state machine source code, so we can focus on SCTUnit specifics here.
The first step is to create a generator model (SGen) for the SCTUnit test, similar to explanation in section "Generating state machine code".
As a result, the generator model file is created. Figure "A sample SCTUnit generator model" shows a generator model for the SCTUnit C generator. The target language is reflected in the generator ID “sctunit::c”. Each SCTUnit test unit is contained in a section marked with the test keyword, followed by the respective fully-qualified test unit name.
A sample SCTUnit SGen model
The SCTUnit C code generator transforms SCTUnit tests into C++ code that makes use of the Google Test unit testing library.
The test code generator can be controlled by the following features:
The Outlet feature is required and determines where the generated artifacts will be placed. Meaning is same as for the common outlet feature.
The SGenModel feature allows to generate a basic generator model file for the statechart under test. The generated .sgen file will take over the FunctionInlining and IdentifierSettings features.
In this context, the FunctionInlining feature is only relevant in combination with the SGenModel feature.
In order to include the proper statechart header and use the proper statechart APIs, the test code generator needs to know which identifier settings are used in the statechart’s generator model. This means, whenever the statechart generator model uses specific IdentifierSettings, the same values need to be set in the test generator model.
In addition, it is possible to specify the file extension of the generated test files with the option testFilenameExtension. The default is ‘cc’.
The LicenseHeader features adds a license header to the generated test files. See also the common LicenseHeader feature.
The OutEventAPI feature is not yet used. It is expected that the statechart uses getters for its out event API. The usage of observers is not yet supported. See also the common OutEventAPI feature.
The JUnitWrapper creates an additional JUnit test that simply runs the generated GTest. This is intended for Java based CI environments.
The SCTUnit C++ code generator transforms SCTUnit tests into C++ code that makes use of the Google Test unit testing library.
The test code generator can be controlled by the following features:
The Outlet feature is required and determines where the generated artifacts will be placed. Meaning is same as for the common outlet feature.
The SGenModel feature allows to generate a basic generator model file for the statechart under test. The generated .sgen file will take over the FunctionInlining and IdentifierSettings features.
In this context, the FunctionInlining feature is only relevant in combination with the SGenModel feature.
In order to include the proper statechart header and use the proper statechart APIs, the test code generator needs to know which identifier settings are used in the statechart’s generator model. This means, whenever the statechart generator model uses specific IdentifierSettings, the same values need to be set in the test generator model.
In addition, it is possible to specify the file extension of the generated test files with the option testFilenameExtension. The default is “cc”.
The LicenseHeader features adds a license header to the generated test files. See also the common LicenseHeader feature.
The OutEventAPI feature is not yet used. It is expected that the statechart uses getters for its out event API. The usage of observers is not yet supported. See also the common OutEventAPI feature.
The JUnitWrapper creates an additional JUnit test that simply runs the generated GTest. This is intended for Java based CI environments.
The GeneratorOptions feature allows to change the behavior of the C++ generator. With the statemachineRefsAsPlainPointer option the generated code in multi-statemachine scenarios will use plain pointers instead of smart pointers for referencing statemachines, just like in the C++ statemachine code generator.
The SCTUnit Java code generator transforms SCTUnit tests into JUnit tests. The generated JUnit tests make use of the Mockito framework when operation mocking is required.
The test code generator can be controlled by the following features:
The Outlet feature is required and determines where the generated artifacts will be placed. Meaning is same as for the common outlet feature.
The Naming feature allows to specify a different package for the generated test class than the statechart class belongs to. If this feature is used, it is required to specify the package of the statechart class in the StatechartNaming feature as described in the next chapter.
Example:
feature Naming {
basePackage = "light.tests"
}
This setting will generate the test class into the light.tests package.
For Java, the generated state machine code on one hand and the SCUnit test classes on the other hand may well belong to different packages. However, in order to be able to execute the state machine, the SCTUnit tests need to know what the state machine’s package is.
This is what the StatechartNaming feature is good for. It contains all the properties you can set in the state machine’s Java code generator Naming feature.
Example:
feature StatechartNaming {
basePackage = "bar.light_switch"
typeName = "CustomLightSwitch"
libraryPackage = "bar.light_switch.lib"
libraryTargetFolder = "src-lib"
}
With these settings, The SCTUnit code generator expects the state machine class to be part the bar.light_switch package. The state machine class name is expected to be CustomLightSwitch. Library artifacts like the timer service interface are expected to be in the src-lib folder under the bar.light_switch.lib package.
The LicenseHeader features adds a license header to the generated test files. See also the common LicenseHeader feature.
The OutEventAPI feature is not yet used. It is expected that the statechart uses getters for its out event API. The usage of observers is not yet supported. See also the common OutEventAPI feature.
The SCTUnit Python code generator transforms SCTUnit tests into Python unittests.
The test code generator can be controlled by the following features:
The Outlet feature is required and determines where the generated artifacts will be placed. Meaning is same as for the common outlet feature.
The Naming feature allows to specify a different package for the generated test class than the statechart class belongs to. If this feature is used, it is required to specify the package of the statechart class in the StatechartNaming feature as described in the next chapter.
Example:
feature Naming {
basePackage = "light.tests"
}
This setting will generate the test class into the light.tests package.
For Python, the generated state machine code on one hand and the SCUnit test classes on the other hand may well belong to different packages. However, in order to be able to execute the state machine, the SCTUnit tests need to know what the state machine’s package is.
This is what the StatechartNaming feature is good for. It contains all the properties you can set in the state machine’s Python code generator Naming feature.
Example:
feature StatechartNaming {
basePackage = "bar.light_switch"
libraryPackage = "bar.light_switch.lib"
libraryTargetFolder = "src-lib"
}
With these settings, The SCTUnit code generator expects the state machine class to be part the bar.light_switch package. Library artifacts like the timer are expected to be in the src-lib folder under the bar.light_switch.lib package.
The LicenseHeader features adds a license header to the generated test files. See also the common LicenseHeader feature.
The OutEventAPI feature is not yet used. It is expected that the statechart uses getters for its out event API. The usage of observers is not yet supported. See also the common OutEventAPI feature.
The JUnitWrapper creates an additional JUnit test that simply runs the generated GTest. This is intended for Java based CI environments.