Back to TTCN-3 Refactoring Catalog
Distribute Test
Transform a non-concurrent test case into a distributed concurrent test case.
Motivation
Non-concurrent TTCN-3 test cases using one main test component (MTC) are called local tests. However, in a distributed architecture, the test system may have to control several interfaces of the SUT and each interface has a different role towards the SUT. In a local test, the behavior is strictly sequential. Hence, several different roles in a local test result in a behavior mixture which is hard to understand and extend. In addition, the concurrent behavior is not adequately modeled. Therefore, it makes sense to distribute a test with different roles across multiple parallel test components (PTCs). Role-specific behavior can be separated through the test components, the behavior mixture of code concerning different roles is reduced.
However, distributed tests are more complicated since they are concurrent. For behavior preservation, it is necessary to synchronize the behavior between the parallel test components. Otherwise, messages may be sent or received when the other test components or the SUT do not expect them due to the missing sequential behavior. For this purpose, each parallel test component must have one or more coordination points where the parallel test components exchange synchronization messages. Due to these messages, there is some overhead involved in the communication though. There are arbitrary different ways to realize this synchronization and the choice highly depends on the test architecture. Therefore, the synchronization is handled universally in the mechanics. The example shows a concrete way to synchronize the PTCs.
This refactoring can be considered a "big" refactoring. Hence, it involves more work and time than the other refactorings presented in this chapter and involves careful considerations and decisions on behalf of the test engineer.
Mechanics
- Identify the different roles of the local test. Find out to which roles each port of the test system interface belongs to.
- For each role, create a component declaration with an appropriate name. Copy the ports belonging to this role into this component declaration. In a local architecture, different roles may share ports. In this case, the shared ports are duplicated in the PTCs.
- Declare synchronization ports for the MTC and the PTCs and include them in the MTC and PTC component declarations accordingly. Make sure the communication directions are supported as needed.
- Each PTC needs to have a startup function from where the behavior is controlled. This function is used later-on as parameter when the MTC starts the behavior on the concurrent PTCs.
- The MTC behavior is described by using the testcase construct which must now contain the test system interface in the system clause.
- In the MTC, add variable declarations (or a set of variables) for each PTC. Add a TTCN-3 statement calling the create operation on the corresponding test component instances and add statements to map their ports to the test system interface. Also, insert statements to connect the synchronization ports as needed.
- Add a TTCN-3 statement to start the PTCs in the MTC.
- Rewrite the test behavior and split it across the PTCs according to their roles.
- Where necessary to maintain the correct ordering, add synchronization points to each PTCs behavior in order to preserve the message exchange order.
- Interleave constructs which were used in the local architecture to simplify the notation of parallel behavior where the order of the received messages did not matter can be simplified in some cases: an alt statement can then be used instead of the interleave construct which is then distributed across the components according to their roles without synchronization.
- If there are multiple branches in the interleave statement using ports in its receiving operation of the matching criteria belonging to the same role, the resulting split behavior belonging to this role must be written in an interleave statement and not in an alt statement. The remaining branches must be handled as described in the following steps.
- If the behavior of a branch in the interleave statement uses ports belonging to different roles, the distribution is not mechanical and needs additional synchronization and an individual behavior distribution.
- If the distributed alt statements contain only a single branch and there is no default altstep activated, the alt statement can be rewritten using standalone statements.
Example
The listing below shows a non-current local test. The test system interface MyTestSystemInterface? contains two ports p1 and p2. It is used in the test case tc_myTestCase. In this test case, the behavior of two roles is mixed.
type component MyTestSystemInterface {
port MyPort1 p1;
port MyPort2 p2;
}
testcase tc_myTestCase() runs on MyTestSystemInterface {
p1.send( a_m1 );
p1.receive( a_m2 );
p2.send( a_m3 );
p1.receive( a_m4 );
interleave {
[] p1.receive( a_m5 ) { p1.send( a_m6 ); }
[] p2.receive( a_m7 ) { p2.send( a_m8 ); }
}
}
After applying the Distribute Test refactoring, the behavior is distributed across the parallel components FirstEntity? and SecondEntity?. Both contain the port p which they use for communication with the SUT. In addition, the PTCs have a synchronization port which is used for the communication with the other PTC. The PTC ports to the SUT are mapped to the test system interface and the synchronization ports are connected to each other.
The actual behavior is distributed to the corresponding components. When the components are started, they are given functions which are then executed on the respective parallel component. These functions are f_firstRoleStartComponent and f_secondRoleStartComponent. The behavior is split according to the ports of the test system interface. In addition, the behavior is synchronized. That way, messages are received and send when they are expected. The synchronization messages contain simple boolean values. In more realistic scenarios, the component synchronization would probably need to send and receive messages containing more information than a boolean value. The interleave construct is rewritten as an alt statement which is split across the PTC behavior. As only a single branch would be left and no default altsteps are activated, the alt statements are written standalone receive operations.
type port SyncPort message {
inout syncmsg;
}
type component MyTestSystemInterface {
port MyPort1 p1;
port MyPort2 p2;
}
type component FirstEntity {
port MyPort1 p;
port SyncPort secondEntitySyncPort;
}
type component SecondEntity {
port MyPort2 p;
port SyncPort firstEntitySyncPort;
}
function f_firstRoleStartComponent() runs on FirstEntity {
p.send( a_m1 );
p.receive( a_m2 );
secondEntitySyncPort.send( boolean:true );
p.receive( a_m4 );
p.receive( a_m5 );
p.send( a_m6 );
}
function f_secondRoleStartComponent() runs on SecondEntity {
firstEntitySyncPort.receive( boolean:true );
p.send( a_m3 );
p.receive( a_m7 );
p.send( a_m8 );
}
testcase tc_myTestCase() runs on MasterComponent system MyTestSystemInterface {
var FirstEntity v_firstRole := FirstEntity.create;
var SecondEntity v_secondRole := SecondEntity.create;
map( v_firstRole:p, system:p1 );
map( v_secondRole:p, system:p2 );
connect( v_firstRole:secondEntitySyncPort, v_secondRole:firstEntitySyncPort);
v_firstRole.start( f_firstRoleStartComponent() );
v_secondRole.start( f_secondRoleStartComponent() );
}
