TcUnit is a unit testing framework made specifically for TwinCAT 3. It’s an open-source project released under a MIT-license. Since the launch of TcUnit the response from users has been overwhelming! I’ve received tons of feedback from individuals and automation engineers from both small and large companies. Judging by the sheer amount of e-mails received over the last half year I would say that there is a big need for a unit testing framework for PLC developers.

The ambition of the initial release was to:

  • show the advantages and concepts of test-driven development in the world of automation
  • get as much feedback as possible, in order to find any potential bugs
  • get improvement suggestions

Although most of the feedback has been positive, one of the things that some people complained about was that there was too much overhead for creating the test suites for the function blocks to be tested. For every test suite it was necessary to write some boilerplate code, so for every test suite it was necessary to:

  1. add pragma {attribute call_after_init}
  2. add the inheritance TcUnit.FB_TestSuite
  3. implement the interface TcUnit.I_RunnableTestSuite (Creating the RunTests method)
  4. create an instance of TcUnit.FB_Assert to do our assertions
  5. have the FB_init constructor, only registering the instance of the FB to the test framework with the line SUPER^.RegisterTestSuite(THIS^); in the body of FB_init

Eventually a user of TcUnit made a fork of TcUnit, making some improvement suggestions and issuing a pull request to include it in the main branch of the framework. With the changes it’s now considerably easier to use the framework. Now all that is necessary is one of the above steps:

  1. add the inheritance TcUnit.FB_TestSuite

In order to create a test in a test-suite, create a test-method:

  • start it with TEST('TestMethodName');
  • end it with TEST_FINISHED();
  • call assert-methods directly, like AssertTrue(MyVariable, 'My message');. There is no need for a standalone assert FB

Then fill the body of your test suite FB with calls to your test methods. You don’t need a RunTests method, and you don’t need to specify when all the tests are finished from your test suite, because that is done on a per-test basis (with TEST_FINISHED() above).

Here’s an example test suite that has one normal test method and one multi-cycle test method:

FB_MyTestSuite Link to heading

FUNCTION_BLOCK FB_MyTestSuite EXTENDS TcUnit.FB_TestSuite
VAR
    CycleTestCounter: UINT := 0;
END_VAR
--------------------------------
Test_OnePlusOne();
Test_OneHundredCycles(ADR(CycleTestCounter));

FB_MyTestSuite.Test_OnePlusOne method Link to heading

METHOD Test_OnePlusOne
VAR
    Result: UINT;
END_VAR
------------------------
TEST('Test_OnePlusOne');
Result := 1 + 1;
AssertEquals_UINT(2, Result, '1 + 1 = 2');
TEST_FINISHED();

FB_MyTestSuite.Test_OneHundredCycles method Link to heading

METHOD Test_OneHundredCycles
VAR_INPUT
    Counter: POINTER TO UINT;
END_VAR
-----------------------------
TEST('Test_OneHundredCycles');
Counter^ := Counter^ + 1;
// This doesn't actually assert anything, but you get the idea
IF Counter^ >= 100 THEN
    TEST_FINISHED();
END_IF

All documentation and examples have been updated on https://www.tcunit.org.

The last release with the previous API was version 0.4.1 and the current version is 0.9.0. Hopefully as much feedback as possible can be gathered, and in a few weeks version 1.0 of TcUnit will be released and the API will be frozen. With the aid of other software developers, TcUnit will constantly be improved upon. The next thing coming soon is the possibility to have the results in xUnit XML format in order to have Jenkins integration for a smooth CI/CD workflow pipeline.

This is what I love with open-source software, that people can exchange ideas and together develop and improve on something. It’s great that test driven development is spreading among automation engineers. I’m happy about the large amount of feedback that automation and software engineers have provided, and I hope we can continue to take further steps in order to take the world of automation out of the dark ages.

Ps.

The developer “aliazzz” has made a fork of TcUnit for vanilla Codesys called CfUnit. I highly encourage all Codesys developers to check it out.

Ds.