This article not only introduces how to introduce TcUnit, a Unit Test Framework, but also briefly explains the concepts of Unit Testing and Test Driven Development. Beckhoff TwinCAT3 is integrated with Visual Studio and various libraries are also freely available from the Community. TcUnit is one of them.
Lets’ start!
Unit testing ?
Unit testing is a software testing technique that aims to verify whether a single component (unit) operates as expected. In unit testing, individual parts such as functions and methods are isolated and tested to confirm whether they function correctly. By performing unit tests, code quality can be improved, bugs can be found earlier, and the time required for fixing them can be reduced. Additionally, through methodologies such as Test-Driven Development (TDD), developers can design and implement code with a focus on code quality.
Image From:
単体テストの概念アイコンソフトウェア開発段階のアイディア細線イラストアプリケーションのパーフォマンス検証java ソース コードit プロジェクトベクトル分離アウトライン描
Working Flow?
The general flow of Unit Testing is as follows. Unit Testing is one of the important methods for continuously testing and ensuring quality.
Step 1: Determine the unit to be tested.
A unit is the smallest testable component of software, such as a function or method. In Beckhoff TwinCAT or Codesys, this would correspond to a Method within a Function or Function Block.
Step 2: Create test cases.
A test case defines a combination of input data and expected output data that verify the expected behavior of the unit under test.
Step 3: Execute the tests.
The unit under test is called using the input data defined in the test cases, and the expected output data is verified.
Step 4: Evaluate the test results.
The test results are evaluated to confirm whether the expected and actual results match.
Step 5: Modify and re-run the tests.
If the test results do not match the expected results, the test cases or the unit under test are modified, and the tests are re-run.
Step 6: Create a test report.
The results of the test execution are reported, showing the successful and failed test cases, and shared with the developers or team members.
Test driven development?
Test Driven Development (TDD) is a software development methodology in which developers write tests before writing code, and repeatedly implement code to pass those tests. TDD consists of the following three steps:
- Test Creation: The developer creates tests that describe the behavior of code that has not yet been implemented.
- Test Execution: The developer executes the created tests and confirms that they fail. At this stage, the tests are guaranteed to fail since the code has not yet been implemented.
- Code Implementation: The developer implements code so that the tests pass. When implementing the code, they follow software development best practices, such as the Single Responsibility Principle.
By repeating this cycle, developers can ensure that the code works as expected and passes the tests. TDD is a useful technique for developing high-quality code quickly and promotes conscious design and implementation of code quality by developers.
Working Flow?
The general flow of Test Driven Development (TDD) is as follows. TDD is an important technique for ensuring that code behaves as expected by focusing on tests.
Step 1: Write Tests
In TDD, we start by writing tests. Tests define the requirements and specifications for the code. We create test cases that include input and output values to check whether the code is working correctly.
Step 2: Run Tests
We run the tests and make sure that they fail. At this point, the code we are testing does not yet exist.
Step 3: Write Code
Once the test has failed, we write the minimum amount of code required to make the test pass. This code needs only to satisfy the minimum requirements.
Step 4: Run Tests Again
We run the tests again to ensure that they pass.
Step 5: Refactor Code
After the test has passed, we refactor the code to remove duplicate code and improve readability. We run the tests again to ensure that they still pass.
xUnit?
xUnit is a type of framework used for performing unit testing in software development. JUnit was the first framework developed, and later, it was ported to multiple languages under the name xUnit, and is now used in many programming languages.
The basic concepts of xUnit are as follows:
- Test case: This is the smallest unit of processing that is grouped together for testing purposes.
- Assertion: This is used to determine whether the test result is as expected or not.
- Test suite: This is a collection of multiple test cases, which can be executed at once.
xUnit provides a structured approach to writing test code, which helps improve the readability and maintainability of the test code. By using the xUnit framework, test automation becomes easier, and developers can benefit from early bug detection and improved quality.
Is TDD always necessary?
Test Driven Development (TDD) is a software development methodology that can be a useful approach for quickly developing high-quality code. However, TDD is not necessary for all projects.
The main benefit of practicing TDD is that it can ensure high-quality code. By writing tests first, the potential for code to cause unexpected errors can be reduced and the time required for corrections can be shortened. In addition, automating tests can reduce the time required for manually performing repetitive testing.
When TDD may not be suitable:
In fact, depending on factors such as program size, requirements, testing environment, and developer skills, implementing Test Driven Development may actually decrease development efficiency. Therefore, while TDD can be a useful approach, it is not necessary to practice it in all projects. For example:
- When the program is small:
If the program is small, the time and effort required to write test cases may outweigh that required for program implementation. In such cases, implementing Test Driven Development can reduce development efficiency. - When program requirements are not defined:
If program requirements are not defined, it may be difficult to write test cases. In such cases, it may be difficult to implement Test Driven Development due to the inability to write test cases. - When the testing environment is not set up:
To implement Test Driven Development, a testing environment is required. However, if the testing environment is not set up, it may be difficult to execute test cases, making it difficult to implement TDD. - When developer skills are insufficient:
To implement Test Driven Development, developers need the skill to write test cases and execute tests. However, if developers lack these skills, it may take longer to write and execute test cases, reducing development efficiency. - When the project schedule is tight:
Since writing test code takes time, if developers are already struggling to meet the project schedule with code development alone, they may not be able to implement TDD. - When implementing TDD for existing code:
In this case, it may be difficult to create tests. Instead of implementing TDD, it may be necessary to consider other methods, such as manual testing or code refactoring.
What about PLC and TDD?
Whether or not Test Driven Development (TDD) is used for PLC programming depends on the nature and requirements of the project. However, TDD can improve the quality and maintainability of PLC programs.
PLC programming is often used in industrial control systems, where safety and reliability are important. Therefore, creating high-quality code is essential. TDD is a useful technique for creating high-quality code, and can also be applied to testing PLC programs. For example, there are tests to confirm that the program is working correctly and refactoring to improve the program’s maintainability.
In addition, manual testing may be necessary to ensure the quality of PLC programs. TDD can help reduce the time required for manual testing.
However, there are some challenges when using TDD for PLC programming. For example:
If the test environment for PLC programs is different from the actual system, test accuracy may decrease.
In order to implement TDD, it is necessary to have a sufficient understanding of the functionality and requirements of PLC programs.
Overall, whether TDD is used for PLC programming depends on project requirements, developer skills, and development environment. However, using TDD can lead to the development of high-quality PLC programs.
Prerequisites?
If using Test Driven Development (TDD) in PLC programming, the following prerequisites are necessary:
- The program’s functionality and requirements are clear.
- A test environment is set up.
- A test framework is selected.
- Utilities or helpers for testing are developed.
What is the development language?
TDD can be used in various PLC programming languages, such as ST, ladder, FBD, and CFC. However, it is necessary to select (or create) a test framework that fits the characteristics of each language.
Example
Here’s a concrete example of using Test Driven Development in PLC programming:
Suppose you’re developing a PLC program to control a machine. You start by defining the following requirements:
The program should read input signals to generate output signals that control the motor.
The program should be able to adjust the motor’s speed by setting parameters.
Based on these requirements, you write the following tests.
- Test to verify if the motor’s output signal is generated correctly:
- Normal motor signal
- Input value: TRUE
- Expected output: Motor output signal turns ON
- Operation: Test if the program’s state is correctly controlled by reading the input signal.
- Normal motor signal
- Test with ON signal from switch:
- Input value: ON
- Expected output: Motor output signal turns ON
- Input value: ON
- Test with OFF signal from switch:
- Input value: OFF
- Expected output: Motor output signal turns OFF
- Test to verify if the parameter for adjusting the motor’s speed is set correctly:
- Input value: Parameter value
- Expected output: Motor speed is adjusted according to the set value.
After writing these tests, the actual implementation of the program is carried out. During implementation, the code for Test 1r Test 2 and Test 3 are written.
Once the implementation is complete, the written tests are executed to confirm that all tests run successfully. If a test fails, it might be a problem with the program’s implementation, and the program needs to be modified.
By using Test Driven Development, program quality can be improved, and bugs can be detected beforehand. Moreover, it leads to higher maintainability and reusability of the program, resulting in an improvement in development efficiency.
Difference between Unit Test and TDD?
Unit Testing and Test Driven Development (TDD) are testing methodologies used in software development, but they take different approaches.
Unit Testing is a technique of executing tests on units (the smallest individual parts) of existing source code to verify their behavior. Developers create test cases for units they have implemented in advance and execute tests to confirm whether or not the unit behaves as expected. Since Unit Testing is executed on already implemented source code, it can be performed regardless of implementation.
On the other hand, TDD is a technique of writing tests first and then implementing them. Developers create test cases first, confirm that they fail, then implement the code to pass those tests, and run the tests again. By repeating this cycle, developers can implement code that meets the requirements. TDD is often associated with creating tests first, leading to improved code quality.
Therefore, Unit Testing is a testing method for testing existing code, while TDD is a method of writing tests before implementation. TDD can also be considered as a type of Unit Testing.
What is Tc Unit?
This article introduces an Open Source Unit Test Framework called TcUnit. TcUnit is an xUnit-type framework created for the development environment of Beckhoff TwinCAT3, and it is a convenient library that User can use by importing TcUnit as a project library.
Please find more information from their website:
Unit testing with TcUnit
Here is the structure of TcUnit.
PRG_Test
PRG_Test is not part of the xUnit concept, it simply provides the necessary execution environment for the test cases to be executed. PRG_Test must be initialized and cannot be used in a production environment, it exists only for testing purposes.
Test case
Test cases are the most basic building blocks of TDD. It is defined by a name that can express the intent and expected result of the test.
Test fixture
A test fixture is a set of prerequisites or states required to run a test. In TcUnit, these are typically constants or variables used as inputs to the function block under test. And you can declare them in test suite or test case depending on whether you want to share these among multiple tests.
Test suite
A test suite is a collection of interrelated tests. TcUnit can share the same test fixture, or you can use separate test fixtures for each test in your test suite. In TcUnit, test suites are defined as Function Blocks.
Assertions
Assertions are methods that verify the state of the unit under test. For example, if you want to make sure that a Function Block’s Output follows a certain state or not, you can assert that the output. TcUnit has various assert methods, such as AssertEquals_INT, AssertEquals_STRING, AssertEquals_BOOL.
Test result formatter
Test result formatter is the part that generates the final result of the test.
Test runner
The TcUnit Test runner ensures that all defined tests are executed and the results collected.
High Light Point!
Easy to use!
TcUnit is an easy-to-use framework. Simply download and install the library, and reference the TcUnit library in your project to begin writing test code.
Just one library!
All the functionality related to unit testing is contained in just one library. Once the library is added to your project, you’re ready to go. You can download a precompiled version of the library for easy installation, or download the source code.
MIT-License
The library and all source code are licensed under the MIT License. This software is completely free and can be used for both personal and commercial use, as long as the MIT License terms are included with the software.
Automated test runs
With the additional TcUnit-Runner software, all TcUnit tests can be integrated into CI/CD software toolchains. By using automation software such as Jenkins or Azure DevOps, tests can be automatically executed and test statistics can be collected whenever something changes in the software version control (such as Git or Subversion).
API
FB_TestSuite
This function block holds the internal state of the test suite. Every test suite can have one or more Tests and can make one or more Asserts. It also provides all assert-methods for asserting various data types. Note that only failed assertions are logged.
Implementation
Download Library
please access this link to download the TcUnit.Library.
https://github.com/tcunit/TcUnit/releases
Install library
First we need to add the library to the TwinCAT project.
Go to References > right click > Add Library.
Click the Advanced.. button.
Click the Library Repository.. Button.
Click the Install button to import the external library into your Environment.
Choose the library that you downloaded before.
Done!TcUnit library is inserted into your project.
Add Library
Now we can add libraries into our project.Go to Reference>Add library.
Choose TcUnit and OK.
When TcUnit is loading into your Project, it changes to a clock mark.
Done!Library is Imported.
FB_SumCal
The first step should be easy, and it is important that anyone can do it. The unit test this time I would like to implement, is the FB_SumCal Function Block. The input parameter is two integers and the output is the sum of the parameters.
FUNCTION_BLOCK FB_SumCal VAR_INPUT i1,i2 :UINT; END_VAR VAR_OUTPUT Result :uint; END_VAR VAR END_VAR Result:=i1+i2; |
So your project now has this structure:
FB_SumCal_Test
Next, add a Function Block FB_SumCal_Test that extends FB_TestSuite. As I explained earlier about FB_TestSuite, this Function Block is a TcUnit API (corresponding to the Test suite part), and various Assertions are implemented inside.
FUNCTION_BLOCK FB_SumCal_Test EXTENDS FB_TestSuite VAR END_VAR ThreePlusThreeEqSix(); |
METHOD ThreePlusThreeEqSix
ThreePlusThreeEqSix actually includes Test Case, Validation, and Test Result Formatter parts.
TEST(TestName:=’ThreePlusThreeEqSix’); FB_SumCal(i1:=3,i2:=3,Result=>Result); AssertEquals( Expected:=ExceptResult ,Actual:=Result ,Message:=’Test Case1:Result is not the same’); FB_SumCal(i1:=5,i2:=3,Result=>Result); AssertEquals( Expected:=ExceptResultCase2 ,Actual:=Result ,Message:=’Test Case2:Result is not the same’); TEST_FINISHED(); |
Here is the flow of the program:
Set the part name to test. This article is ThreePlusThreeEqSix.
TEST(TestName:=’ThreePlusThreeEqSix’); |
Run the code you want to test. In this article, FB_SumCal, i1=3,i2=3, so the result is 6.
FB_SumCal(i1:=3,i2:=3,Result=>Result); |
Then we will use AssertEquals to validate results. The expected output and the current output result are verified, and if they do not match, the Test Case1: Result is not the same message is printed.
AssertEquals( Expected:=ExceptResult ,Actual:=Result ,Message:=’Test Case1:Result is not the same’); |
Finish the Test.
TEST_FINISHED(); |
So your project now has this structure:
POU_Test
Next, create a POU and run TcUnit to start testing.
PROGRAM POU_Test VAR FB_SumCal_Test :FB_SumCal_Test; END_VAR TcUnit.RUN(); |
So your project now has this structure:
MAIN
The last is MAIN POU, and we just need to call POU_Test().
PROGRAM MAIN VAR END_VAR POU_Test(); |
Finally, the project structure will change as shown below.
Result
If one of the two tests fails, only the failing test will be printed, as explained earlier.
If two tests fail, Successful Test: 0 will be displayed and a message for each Test Result will also be printed.