Unit testing and PLCs

TcUnit is a unit testing framework made specifically for TwinCAT3. 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:


    CycleTestCounter: UINT := 0;


FB_MyTestSuite.Test_OnePlusOne method

METHOD Test_OnePlusOne
    Result: UINT;

Result := 1 + 1;
AssertEquals_UINT(2, Result, '1 + 1 = 2');

FB_MyTestSuite.Test_OneHundredCycles method

METHOD Test_OneHundredCycles
    Counter: POINTER TO UINT;

Counter^ := Counter^ + 1;
// This doesn't actually assert anything, but you get the idea
IF Counter^ >= 100 THEN

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.


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


  • Share on:

3 Comments, RSS

  1. Roald

    Nice project and thanks for diong this.

    Initially I started to use the unit test library from Stefan Henneken, but that is not really developed any longer I think. Also it needs quite some boiler plate. It seems that with the latest additions your library clearly wins from the usability perspective. I’m making the switch!

    One question: why does the final report of all finished tests show up as ADS error messages, even when all tests pass? Wouldn’t it be better to have it as a normal ads message, or a warning?

    • Jakob Sagatowski

      Hi Roald and thanks for your feedback.
      To answer your question it’s for two reasons. Most developers I’ve seen working with TwinCAT3 suppress the message-type, but always keep the errors visible. Also, I thought the messages could be used for future expansion of functionality such as progress feedback of running the tests.

  2. Peter

    Hi Jacob! Very nice framework.
    I’m looking for using the framework for 2 or more Subject Under Tests.
    Can the current framework support it? If not, what is the best way to upgrade it? Please advise!

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.