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.
Kernel tests pass | Codesys tests pass | B&R tests pass |
---|---|---|
100.0 | 100.0 | 100.0 |
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.
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.
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.
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.
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
We appreciate feedback and contribution to this repo! Before you get started, please see the following:
Include information on how to get support. Consider adding:
Published under the MIT license