/UniTest

UniTest is a PLC, IDE and manufacturer agnostic library written using only the features of the IEC61131 standard. Due to this, portability to all IEC compliant platforms is straightforward and can be achieved programmatically or with minimal manual work.

Primary LanguageSmalltalkMIT LicenseMIT

UniTest (Universal PLC Unit Test Library)

LOGO

UniTest is a PLC, IDE and manufacturer agnostic library written using only the features of the IEC61131 standard. Due to this, portability to all IEC compliant platforms is straightforward and can be achieved programmatically or with minimal manual work.

Last release test results

Kernel tests pass Codesys tests pass B&R tests pass
100.0 100.0 100.0

Main idea

The platform agnostic library, further known as the Kernel, is provided in the JSON format so it can be accessed programmatically by the programmers language of choice. The Kernel is a definition of all POUs and Data types that needs to be recreated in the target system. It holds the assert functions library and a collection of test PRGs. The Kernel is succesfully ported when all PRGs on the target system pass all tests.

DIAG

Why you should use it

There are many frameworks for unit testing in the world but in the Industrial automation world there are virtually none. Certain PLC orientated UnitTest frameworks do exists but they are often designed for specific IDEs. Those frameworks are then incompatible with other platforms. If your platform doesn't have any unit testing frameworks developed, here is where this library tries to help.

Documentation

Each assert function has a software description accessible by browsing this repositories docs folder. The documentation link is also included in the Kernel and in the code itself.

Usage of the unit test framework

The usage of the library can be checked from the included programs in the Library_tests namespace which can be imported into the target system. The library end user can implement the assert functions into its own solutions. As the library and the test cases are written in structured text the test cases are easily programmatically created. For example, the test cases for all the library assert functions were written by a script.

General example of POU under test case

INTERFACE
    VAR 
        vTestCases : ARRAY[0..20] OF utTestCase; (*Definition of all test cases for this POU*)
        testRunner : utTestSuite; (*Test Suite fb instance to run the tests*)
        vTestCase1_act : INT; (*Data 1 of test case 1*)
        vTestCase1_xp : INT; (*Data 2 of test case 1*)
        vTestCase2_act : INT; (*Data 3 of test case 2*)
        vTestCase2_xp : INT; (*Data 4 of test case 2*)
    END_VAR
END_INTERFACE
PROGRAM _CYCLIC
    (*Run the test cases*)
    (*Here the tests have been divided into actions for easier programming and readability*)
    (*Test 1 components*)
    TEST_CASE_1_SETUP; (*Here we set up the variables, pou under test etc*)
    TEST_CASE_1_EXEC; (*Here we actually conduct the test*)

    TEST_CASE_2_SETUP;
    TEST_CASE_2_EXEC;

    (*Instantiate the test runner*)
    testRunner(
    	Id := 138, (*Id must be unique. This is also used to write the results to a unique index of the global results variable*)
    	Name := 'fcSumTwoNumbers', (*Name of the POU under test*)
        RunTests := gRunAll, (*Flag to start the tests*)
    	ResetTests := gResetAll, (*Flag to reset the tests*)
    	TestCases:=vTestCases); (*Local test case results array for summarizing*)

    (*Report the results to the collection global. The global can be used to keep track of all POUs under test and 
    their results.*)
    gResults[testRunner.Id] := testRunner.Summary;
END_PROGRAM
ACTION TEST_CASE_1_SETUP :
    IF vTestCases[0].state = ut_SETUP THEN
    	(*Setup the test case information*)
    	vTestCases[0].id := 1; (*Id is to identify this test from the rest of this POUs tests*)
        (*Description fields are available to describe what the test is all about and additional information*)
    	vTestCases[0].desc[0] := 'CHECK SUM 1 +1';
        vTestCases[0].desc[1] := 'Tests if the function returns the sum of 1+1 as 2';
    	vTestCases[0].desc[2] := 'PASS IF: 1+1 = 2';
    	
    	(*Setup needed variables*)
    	vTestCase1_act:= 0;
    	vTestCase1_xp := 2;

        (*The program is waiting in this action until the user starts the test. The test runner changes the vTestCases[0].state to utRunning*)
    END_IF
END_ACTION
ACTION TEST_CASE_1_EXEC :
    IF vTestCases[0].state = ut_RUNNING THEN
    	(*Run the test case*)
        vTestCase1_act = fcSumTwoNumbers(1, 1);

    	IF assertEqual_INT(vTestCase1_act, vTestCase1_xp) THEN
    		vTestCases[0].state := ut_PASSED;
    		vTestCases[0].msg := 'Expected == Returned -> PASS';
    	ELSE
    		vTestCases[0].state := ut_FAILED;
    		vTestCases[0].msg := 'Expected <> Returned -> FAIL';
    	END_IF
    END_IF
    (*The test case changes the state to ut_PASSED or ut_FAILED. This can be used if the test needs to be conducted
    in multiple cycles.
    When the user clicks the reset all button the test moves to the ut_SETUP state and can be started again*)
END_ACTION
ACTION TEST_CASE_2_SETUP :
    IF vTestCases[1].state = ut_SETUP THEN
    	(*Setup the test case information*)
    	vTestCases[1].id := 2;
    	vTestCases[1].desc[0] := 'CHECK SUM -1 + 9';
        vTestCases[1].desc[1] := 'Tests if the function returns the sum of -1+9 as 8';
    	vTestCases[1].desc[2] := 'PASS IF: -1+9 = 8';
    	
    	(*Setup needed variables*)
    	vTestCase2_act := 0;
    	vTestCase2_xp := 8;
    END_IF
END_ACTION
ACTION TEST_CASE_2_EXEC :
    IF vTestCases[1].state = ut_RUNNING THEN
    	(*Run the test case*)
        vTestCase2_act = fcSumTwoNumbers(-1, 9);

    	IF assertEqual_INT(vTestCase2_act, vTestCase2_xp) THEN
    		vTestCases[1].state := ut_PASSED;
    		vTestCases[1].msg := 'Expected == Returned -> PASS';
    	ELSE
    		vTestCases[1].state := ut_FAILED;
    		vTestCases[1].msg := 'Expected <> Returned -> FAIL';
    	END_IF
    END_IF
END_ACTION

Assert functions available

C C C C C
assertFalse assertEqual_BOOL assertEqual_BYTE assertEqual_WORD assertEqual_DWORD
assertEqual_LWORD assertEqual_SINT assertEqual_USINT assertEqual_INT assertEqual_UINT
assertEqual_DINT assertEqual_UDINT assertEqual_LINT assertEqual_ULINT assertEqual_REAL
assertEqual_LREAL assertEqual_STRING assertEqual_WSTRING assertEqual_TIME assertEqual_LTIME
assertEqual_DATE assertEqual_LDATE assertEqual_DATE_AND_TIME assertEqual_LDATE_AND_TIME assertEqual_TIME_OF_DAY
assertEqual_LTIME_OF_DAY assertNotEqual_BOOL assertNotEqual_BYTE assertNotEqual_WORD assertNotEqual_DWORD
assertNotEqual_LWORD assertNotEqual_SINT assertNotEqual_USINT assertNotEqual_INT assertNotEqual_UINT
assertNotEqual_DINT assertNotEqual_UDINT assertNotEqual_LINT assertNotEqual_ULINT assertNotEqual_REAL
assertNotEqual_LREAL assertNotEqual_STRING assertNotEqual_WSTRING assertNotEqual_TIME assertNotEqual_LTIME
assertNotEqual_DATE assertNotEqual_LDATE assertNotEqual_DATE_AND_TIME assertNotEqual_LDATE_AND_TIME assertNotEqual_TIME_OF_DAY
assertNotEqual_LTIME_OF_DAY assertGreater_BYTE assertGreater_WORD assertGreater_DWORD assertGreater_LWORD
assertGreater_SINT assertGreater_USINT assertGreater_INT assertGreater_UINT assertGreater_DINT
assertGreater_UDINT assertGreater_LINT assertGreater_ULINT assertGreater_REAL assertGreater_LREAL
assertGreater_TIME assertGreater_LTIME assertGreater_DATE assertGreater_LDATE assertGreater_DATE_AND_TIME
assertGreater_LDATE_AND_TIME assertGreater_TIME_OF_DAY assertGreater_LTIME_OF_DAY assertGreaterEqual_BYTE assertGreaterEqual_WORD
assertGreaterEqual_DWORD assertGreaterEqual_LWORD assertGreaterEqual_SINT assertGreaterEqual_USINT assertGreaterEqual_INT
assertGreaterEqual_UINT assertGreaterEqual_DINT assertGreaterEqual_UDINT assertGreaterEqual_LINT assertGreaterEqual_ULINT
assertGreaterEqual_REAL assertGreaterEqual_LREAL assertGreaterEqual_TIME assertGreaterEqual_LTIME assertGreaterEqual_DATE
assertGreaterEqual_LDATE assertGreaterEqual_DATE_AND_TIME assertGreaterEqual_LDATE_AND_TIME assertGreaterEqual_TIME_OF_DAY assertGreaterEqual_LTIME_OF_DAY
assertLess_BYTE assertLess_WORD assertLess_DWORD assertLess_LWORD assertLess_SINT
assertLess_USINT assertLess_INT assertLess_UINT assertLess_DINT assertLess_UDINT
assertLess_LINT assertLess_ULINT assertLess_REAL assertLess_LREAL assertLess_TIME
assertLess_LTIME assertLess_DATE assertLess_LDATE assertLess_DATE_AND_TIME assertLess_LDATE_AND_TIME
assertLess_TIME_OF_DAY assertLess_LTIME_OF_DAY assertLessEqual_BYTE assertLessEqual_WORD assertLessEqual_DWORD
assertLessEqual_LWORD assertLessEqual_SINT assertLessEqual_USINT assertLessEqual_INT assertLessEqual_UINT
assertLessEqual_DINT assertLessEqual_UDINT assertLessEqual_LINT assertLessEqual_ULINT assertLessEqual_REAL
assertLessEqual_LREAL assertLessEqual_TIME assertLessEqual_LTIME assertLessEqual_DATE assertLessEqual_LDATE
assertLessEqual_DATE_AND_TIME assertLessEqual_LDATE_AND_TIME assertLessEqual_TIME_OF_DAY assertLessEqual_LTIME_OF_DAY assertArrayEqual_BOOL
assertArrayEqual_BYTE assertArrayEqual_WORD assertArrayEqual_DWORD assertArrayEqual_LWORD assertArrayEqual_SINT
assertArrayEqual_USINT assertArrayEqual_INT assertArrayEqual_UINT assertArrayEqual_DINT assertArrayEqual_UDINT
assertArrayEqual_LINT assertArrayEqual_ULINT assertArrayEqual_REAL assertArrayEqual_LREAL assertArrayEqual_STRING
assertArrayEqual_WSTRING assertArrayEqual_TIME assertArrayEqual_LTIME assertArrayEqual_DATE assertArrayEqual_LDATE
assertArrayEqual_DATE_AND_TIME assertArrayEqual_LDATE_AND_TIME assertArrayEqual_TIME_OF_DAY assertArrayEqual_LTIME_OF_DAY assertTrue
utTestSuite utTestReporter

Contributing

We appreciate feedback and contribution to this repo! Before you get started, please see the following:

Support + Feedback

Include information on how to get support. Consider adding:

  • Use Issues for code-level support
  • Use Community for usage, questions, specific cases

License

Published under the MIT license