Skip to main content

Implementing a multitest POU

To implement many related test cases on only one test POU, you can use the pragma {attribute 'test' := 'multitest'}, which decorate a test POU with ETrigA behavior with this pragma. Additional information is reported when logging during the test run. For example, the test result for each individual test case is reported and logged. Additional inputs and outputs are required for this.

Creating a framework for the test case

A test case is a function block which follows a specific scheme (Interface convention). To make it easier to follow the scheme, we recommend using the wizard. If you manually create a test case, then it should always derive from TM.BaseMultiTest. (Obsolete Test POU Implementations).

Procedure. Inserting a unit test object
  1. Add the test case function block below an application or in the POU pool. To do this, click Add ObjectUnit Test in the context menu.

    The Add Unit Test dialog opens.

  2. Specify the following parameters:

    Name of the POU: TestCase_1

    Type: Extend TM.BaseMultiTest

    _cds_icon_checked.png Add optional methods

    _cds_icon_checked.png Add implementation template

    _cds_icon_checked.png Add explanatory comments

    Test Case Category: BasicTests

  3. Click Add to confirm the dialog and save the project.

    A new function block is created with the Execute, Setup, Teardown, and Abort methods.

For more information, see: Add Unit Test

Implementing the test logic

  1. In the declaration part of the test POU, declare an instance of the testee. The example tests a CTU standard POU.

    {attribute 'test':='multitest'}
    {attribute 'testcasetimeout':='15000'}
    FUNCTION_BLOCK CTU_TestCases EXTENDS TM.BaseMultiTest
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    	counter : CTU;	// testee instance
    	cycleCounter : INT; // independant test control variable
    END_VAR
  2. In the implementation part of the prvStart method, initialize the testee. The example initializes the CTU via reset and, if necessary, initializes variables which are required for the test flow. The prvStart method is called one time before prvCyclicAction.

    SUPER^.prvStart();  // mandatory - don't change
    
    // Initialize the testee
    counter(CU:=FALSE, RESET:=TRUE);	// reset the counter; note: initialization per test case must be performed in prvCyclicAction
    
    // Initialize test control variable for first test case
    cycleCounter := 0;
  3. Call the testee and check its result in the prvCyclicActionmethod. The prvCyclicAction method is called until xDone is set to TRUE. As a result, the testee can be called over multiple PLC cycles for each test case. The following example implements only one test case (CASE 0), but other test cases can easily be added according to this scheme.

    SUPER^.prvCyclicAction();   // mandatory - dont' change
     
    CASE diTestCaseIndex OF
        0:	// test case #0; add test cases as new CASEs
            IF xGetTestInfo THEN    // initializate test case
                //  define mandatory test case information
                wsTestCaseName := "Count from 0 to 5";
                wsTestCaseCategories := "CTU";
                diTestCaseTimeout := 500;
                 
                xDone := TRUE;  // signal test case initialization done
            ELSE
    			IF (cycleCounter = 0) THEN
    				// prepare test case
    				counter(CU:=FALSE, RESET:=TRUE, PV:=5);	
    	
    			// stimulate testee as long as we expect: 5 rising edges to count to 5 equals minimum 10 cycles
    			ELSIF (cycleCounter < 10) THEN 
    				// check: counter limit not reached yet
    				TM.Assert_Bool_IsFalse(THIS^, counter.Q, "Counter signaled limit reached prematurely");
    				TM.Assert_Word_Less(THIS^, 5, counter.CV, "Counter reached limit unexpectedly");	
    			
    				counter(CU:= (NOT counter.CU), RESET:=FALSE);	// Trigger counter with rising edges
    				
    				xDone := FALSE;   // signal test still running
    			ELSE
    				// check: counter reached limit
    				TM.Assert_Bool_IsTrue(THIS^, counter.Q, "Counter did not signal limit reached");
    				TM.Assert_Word_Equal(THIS^, 5, counter.CV, "Counter did not reach limit");
    				 
    				xDone := TRUE;    // mandatory: signal test completed
    			END_IF
    			
    			IF xDone OR xError THEN
    				cycleCounter := 0;	// clean up for next test case
    			ELSE
    				cycleCounter := cycleCounter + 1;	// count cycles	
    			END_IF
            END_IF
        ELSE
            IF diTestCaseIndex < 0 THEN  // mandatory, signal number of test cases, if called with (diTestCaseIndex < 0)
                diTestCaseCount := 1;
                xDone := TRUE;
            ELSE    // mandatory, don't remove: fail for invalid test case index
                wsInfo := "Invalid test case index";
                iError := 2;
            END_IF
    END_CASE
  4. Uninitialization of the test could be done in the prvResetOutputs method, but it is not necessary in this example. Delete the method.

  5. No special code needs to be executed if the test times out. Delete the prvAbort method.

Configuring the IEC Unit Test test element

  1. In Title, specify a test case name.

    Example: N_001_Test

    When the test is executed, the title is displayed as the test case name.

  2. In Device read timeout (ms), specify a time. Example: 5000

    Note: For each test case, the system waits at least for the test status and test result to be read. Moreover, the system waits when the application status is read when logging in.

    Before reading variables, the system waits until the timeout has elapsed.

  3. In Login retries, specify the maximum number of attempts to establish online mode. Example: 3

  4. Click in the upper left of the Selected categories table.

    An input field is displayed.

  5. Specify all categories which are called in this test case.

    Example: BasicTests

    All test function blocks decorated with the 'testcategory' := 'BasicTests' attribute called in this test case.

Executing a test script

Requirement: The test environment is prepared. The test project is open in the CODESYS Development System and the device settings and communication settings are configured. These required instructions are made by a preparatory test case in the test script.

  1. The contents of the Generated unit tests folder are deleted in order to remove the generated POUs of a previous IEC unit test.

  2. The project and the Libraries to test are searched for test tables. Then the function blocks for executing the test tables are generated in the Generated unit tests folder.

  3. The project and the libraries that are listed on Libraries to test are searched for function blocks and programs that are marked as test.

    Then, of the detected programs and function blocks, those are removed which are excluded by the test categories or the name.

    The remaining programs and function blocks are used in the test.

  4. A backup of the original application is created.

  5. The test cases are inserted as IEC code in PLC_PRG. In the declaration and implementation, the added lines of code are marked with Generated by IEC Unit Test.

  6. If the application is too large for the controller, then the tests are subdivided automatically into several smaller applications. One or more executable applications are generated.

  7. The test is executed with the generated application(s).

    The following steps are processed:

    • A test application is downloaded to the controller.

    • The test application is started.

    • The test cases are executed. If the string variable sInfo or wsInfo is used in the test function blocks, then its contents are included in the test report.

    • The timeout is monitored.

    • The test result is checked and then sent to the test report.

    • The application is removed from the controller.

  8. A test report is generated containing all test results.

Tip

Pay attention to the test repository Default-Test-Repository. The test script Examples.IecUnitTest is located there. In this test script, you can reconstruct an IEC unit test by loading the prepared steps to the project, updating the device, and configuring the communication settings. This is followed by an IEC unit test. The test cases that are processed originate from the Example_IecUnitTest project. The project is included in the installation directory: C:\ProgramData\CODESYS Test Manager.

The sample scripts are created for a CODESYS Control Win controller (32 bit). If you use a different controller, then you need to adapt the parameters of the UpdateDevice script action.