A minimal TTCN-3 SIP Abstract Test Suite tutorial

by Bernard Stepien, University of Ottawa,
bernard@site.uottawa.ca

Motivation

TTCN-3 is a very powerful test specification language. It is based on the principle of separation of concerns, thus dividing a test specification into two separate parts:

part 1. The Abstract Test Suite (ATS) that defines the sequences of test events and their associated test verdicts

part 2. The test adaptation layer that handles communications with the System Under Test (SUT) and provides the necessary coding and decoding of messages flowing between the test suite and the SUT.

Newcomers to TTCN-3 are known to have some difficulty with the test adapter part. Thus, the idea here is to write a minimal example of a purely abstract SIP test suite where the SUT that portrays the SIP server is yet another TTCN-3 abstract component. This will enable the reader to study all the concepts involved in the ATS such as test cases, parallel test components, data typing, template definitions and the TTCN-3 matching mechanism without having to go through the pain of writing a test Adapter. A brief overview of the steps involved in writing a test adapter will follow soon.

This SIP abstract test suite is very minimal, thus enabling any newcomer to TTCN-3 to get acquainted to TTCN-3 concepts without having to go through tens of thousands of lines of code. The full SIP test suite is available from ETSI and test adapter are available from TTCN-3 tool vendors.

The following test suite is fully executable, thus enabling the reader to follow through an execution and inspect the content of messages and observe the TTCN-3 matching mechanism at work.

If you landed on this page directly and you may wonder what TTCN-3 is all about, click on any of the following links to obtain unlimited tutorials and papers on the subject:

Overview

The test case presented here is derived from the following Message Sequence Chart that depicts a complete simple SIP session.

The specification of the caller's and the callee's behavior fully reflect how a real SIP test suite would look like. The specification of the SIP server is purely fictional and is here only to avoid the specification the the test adapter that would communicate with the SUT.

Minimal SIP Abstract Test Suite

The following Abstract Test Suite (ATS) can be downloaded by clicking here.
module MiniSIP {

	// written by Bernard Stepien, University of Ottawa
	// questions can be forwarded to: bernard@site.uottawa.ca

	type record SIP_resquestType {
		charstring method,
		charstring version,
		charstring fromField,
		charstring toField
	}
					
	type record SIP_Status_responseType {
		charstring sipVersion,
		integer statusCode,
		charstring reasonPhrase,
		charstring fromField,
		charstring toField
	}
	
	type port CoordinationPortType message {
		inout charstring;
	}
	
	type port SipPortType message {
		inout SIP_resquestType, SIP_Status_responseType;
	}
	
	type component MTCType {
		port CoordinationPortType coord_port_server;
		port CoordinationPortType coord_port_caller;
		port CoordinationPortType coord_port_callee;
	}
	
	type component ServerType {
		port CoordinationPortType coord_port;
		port SipPortType sipPort_1;
		port SipPortType sipPort_2;	
	}
	
	type component PTCType {
		port CoordinationPortType coord_port;
		port SipPortType sipPort;
	}
	
	type component SystemType {
		
	}
	
	///////////////////////////////////////
	// templates definitions
	//////////////////////////////////////
	
	template SIP_resquestType caller_SIP_Invite_Request := {
		method := "INVITE",
		version := "SIP/2.0",
		fromField := "caller@abcd.com",
		toField := "callee@efgh.com"
	}

	template SIP_resquestType SIP_Invite_Request := {
		method := "INVITE",
		version := "SIP/2.0",
		fromField := ?,
		toField := ?
	}
	
	template SIP_resquestType caller_SIP_Bye_Request := {
		method := "BYE",
		version := "SIP/2.0",
		fromField := "caller@abcd.com",
		toField := "callee@efgh.com"
	}

	template SIP_resquestType callee_SIP_Bye_Request := {
		method := "BYE",
		version := "SIP/2.0",
		fromField := "callee@abcd.com",
		toField := "caller@efgh.com"
	}
	
	template SIP_resquestType SIP_Bye_Request := {
		method := "BYE",
		version := "SIP/2.0",
		fromField := ?,
		toField := ?
	}
	
	template SIP_resquestType caller_SIP_Ack_Request := {
		method := "ACK",
		version := "SIP/2.0",
		fromField := "caller@abcd.com",
		toField := "callee@efgh.com"
	}
	
	template SIP_resquestType SIP_Ack_Request := {
		method := "ACK",
		version := "SIP/2.0",
		fromField := ?,
		toField := ?
	}
		
	template SIP_Status_responseType SIP_200_OK_Response := {
		sipVersion := "SIP/2.0",
		statusCode := 200,
		reasonPhrase := "OK",
		fromField := "caller@abcd.com",
		toField := "callee@efgh.com"
	}
	
	template SIP_Status_responseType SIP_100_Trying_Response := {
		sipVersion := "SIP/2.0",
		statusCode := 100,
		reasonPhrase := "Trying",
		fromField := "caller@abcd.com",
		toField := "callee@efgh.com"
	}
	
	template SIP_Status_responseType SIP_180_Ringing_Response := {
		sipVersion := "SIP/2.0",
		statusCode := 180,
		reasonPhrase := "Ringing",
		fromField := "caller@abcd.com",
		toField := "callee@efgh.com"
	}
	
	//////////////////////////////////////////
	// test behaviors
	/////////////////////////////////////////

	function serverBehavior() runs on ServerType {
		var SIP_resquestType inviteRequest;
		var SIP_resquestType byeRequest;
		var SIP_resquestType ackRequest;
		var SIP_Status_responseType ringingResponse;
		var SIP_Status_responseType OKResponse;
		log("running server");
		
		alt {
			[] sipPort_1.receive(SIP_Invite_Request) -> value inviteRequest	{
				sipPort_1.send(SIP_100_Trying_Response);
				if(inviteRequest.toField == "callee@efgh.com") {
					sipPort_2.send(inviteRequest);
				}
				repeat
			}
			[] sipPort_1.receive(SIP_200_OK_Response) -> value OKResponse {
				if(OKResponse.fromField == "callee@efgh.com") {
					sipPort_2.send(OKResponse);
				}
				repeat
			}
			[] sipPort_1.receive(SIP_180_Ringing_Response) -> value ringingResponse {
				if(ringingResponse.fromField == "callee@efgh.com") {
					sipPort_2.send(ringingResponse);
				}
				repeat 
			}
			[] sipPort_1.receive(SIP_Bye_Request) -> value byeRequest	{
				if(byeRequest.toField == "callee@efgh.com") {
					sipPort_2.send(byeRequest);
				}
				repeat
			}
			[] sipPort_1.receive(SIP_Ack_Request) -> value ackRequest	{
				if(ackRequest.toField == "callee@efgh.com") {
					sipPort_2.send(ackRequest);
				}
				repeat
			}

			[] sipPort_2.receive(SIP_Invite_Request) -> value inviteRequest	{
				sipPort_2.send(SIP_100_Trying_Response);
				if(inviteRequest.toField == "caller@abcd.com") {
					sipPort_1.send(inviteRequest);
				}
				repeat
			}
			[] sipPort_2.receive(SIP_200_OK_Response) -> value OKResponse {
				if(OKResponse.fromField == "caller@abcd.com") {
					sipPort_1.send(OKResponse);
				}
				repeat
			}
			[] sipPort_2.receive(SIP_180_Ringing_Response) -> value ringingResponse {
				if(ringingResponse.fromField == "caller@abcd.com") {
					sipPort_1.send(ringingResponse);
				}
				repeat 
			}
			[] sipPort_2.receive(SIP_Bye_Request) -> value byeRequest	{
				if(byeRequest.toField == "caller@abcd.com") {
					sipPort_1.send(byeRequest);
				}
				repeat
			}
			[] sipPort_2.receive(SIP_Ack_Request) -> value ackRequest	{
				if(ackRequest.toField == "caller@abcd.com") {
					sipPort_1.send(ackRequest);
				}
				repeat
			}
			[] coord_port.receive("shutdown") {
				stop
			}
			[] sipPort_1.receive {
				setverdict(fail)
			}
			[] sipPort_2.receive {
				setverdict(fail)
			}
		}
	}
	
	
	
	function callerBehavior() runs on PTCType {
		log("running caller");
		
		sipPort.send(caller_SIP_Invite_Request);
		
		alt {
			[] sipPort.receive(SIP_100_Trying_Response) {
				alt {
					[] sipPort.receive(SIP_180_Ringing_Response) {
						alt {
							[] sipPort.receive(SIP_200_OK_Response) {
								sipPort.send(caller_SIP_Bye_Request);
								alt {
									[] sipPort.receive(SIP_200_OK_Response) {
										sipPort.send(caller_SIP_Ack_Request);
										setverdict(pass)
									}
									[] failures() {
										setverdict(fail)
									}
								}		
							}
							[] failures() {
								setverdict(fail)
							}
						}
					}
					[] failures(){
						setverdict(fail)
					}
				}
			}
			[] failures() {
				setverdict(fail)
			}
		}					
	}
	
	function cooperativeCalleeBehavior() runs on PTCType {
		log("running callee");
		
		sipPort.receive(caller_SIP_Invite_Request);
		sipPort.send(SIP_180_Ringing_Response);
		sipPort.send(SIP_200_OK_Response);
		
		sipPort.receive(caller_SIP_Bye_Request);
		sipPort.send(SIP_200_OK_Response);
		sipPort.receive(caller_SIP_Ack_Request);
		setverdict(pass)
	}
	
	//////////////////////////////////////////////////////////////////
	// test cases
	/////////////////////////////////////////////////////////////////
				
	testcase inviteSIP_test() runs on MTCType system SystemType {
		var PTCType callerPTC;
		var PTCType calleePTC;
		var ServerType serverTC;
		
		serverTC := ServerType.create;
		callerPTC := PTCType.create;
		calleePTC := PTCType.create;

		connect(callerPTC:sipPort, serverTC:sipPort_1);
		connect(calleePTC:sipPort, serverTC:sipPort_2);
		
		connect(mtc:coord_port_callee, calleePTC.coord_port);
		connect(mtc:coord_port_caller, callerPTC.coord_port);
		connect(mtc:coord_port_server, serverTC.coord_port);
		
		serverTC.start(serverBehavior());
		callerPTC.start(callerBehavior());
		calleePTC.start(cooperativeCalleeBehavior());
		
		callerPTC.done;
		calleePTC.done;
		log("caller and callee processes done");
		coord_port_server.send("shutdown");
		serverTC.done;
		
	}

	altstep failures() runs on PTCType {
		
		[] any timer.timeout {
			setverdict (fail);
		}
		
		[] any port.receive {
			setverdict (fail);
		}
	}


}

compiling and running the Abstract Test Suite

The above test suite compiles and executes with any TTCN-3 tool. The execution of this test suite should produces the following message sequence chart:

what is the next step ?

Well, now that you have fully mastered the art of TTCN-3 Abstract Test Suite specification, you can now extend this mininal abstract test suite and add functionality to it. Now, all you need to become a TTCN-3 expert is to specify the corresponding test adapter. A test adapter is specified using a standard abstract class TestAdapter. The content of a test adapter and especially of the codecs currently depends entirely on tool vendors API. API have not yet been standardized and exist in various implementation languages such as C, C++ and Java. Experiments have been conducted using Python.

APIs provide Classes for basic datatypes like string, integer, the TTCN-3 record, list, set or union types along with methods to create and populate their various attributes. Refer to the vendors documentation for further study.