Tc.Prober gives you the possibility to unit test your TwinCAT3 plc code using unit testing frameworks (such as NUnit, XUnit, etc.). It provides runners that execute plc code remotely and allow you to arrange-act-assert in .net environment. This approach brings the advantage of using well-evolved testing frameworks and tools in unit testing of the plc code.
- TwinCAT3 4024.10
- VS2019
- nUnit/xUnit
- Inxton.Package.Vortex.Essentials
Takes your plc program and trans-piles it into .net accessible twin represented as clr/.net objects that are accessible from any .net application. It is as if you referenced plc project in a .net project. You can read more here.
Inxton developer license is free and grants full functionality. It limits the run of the program to a period of up to 2 hours. After this period, the restart is required. You can get the license at inxton.com. For the unit-testing project, the developer license is sufficient.
Any method with {attribute 'TcRpcEnable'}
is trans-piled by the IVF as clr/.net method of its function block and makes it invocable from .net environment. The methods return type
and parameters
must be of primitive type.
Example plc method
{attribute 'TcRpcEnable'}
METHOD RunCount : UINT
VAR_INPUT
resetCounter : BOOL;
END_VAR
VAR_INST
runs : UINT;
END_VAR
-------------------------------------------------
IF(resetCounter) THEN runs := 0; RETURN; END_IF;
runs := runs + 1;
RunCount := runs;
The method will be rendered available into .net by IVF compiler.
Calling the method from C#
public ushort RunCount(System.Boolean resetCounter)
{
return (ushort)Connector.InvokeRpc("Tests._basicRunnerTests", "RunCount", new object[]{resetCounter});
}
Unit testing
[Test]
[TestCase((ushort)10)]
[TestCase((ushort)11)]
public void basic_runner_tests_run_count(ushort expected)
{
//-- Arrange
var sut = Entry.Plc.Tests._basicRunnerTests;
sut.RunCount(true);
//-- Act
var actual = sut.Run((A) => A.RunCount(false), expected);
//-- Assert
Assert.AreEqual(expected, (ushort)actual);
}
The runner can be run in recording/replaying mode. This allows to record I/O image during testing with the hardware and to replay it later when the hardware is no longer available.
Testing with recording
public void RecordAndReplayTest()
{
var sut = connector.Tests._recorderRunnerTests;
IRecorder actor;
// We run with recording
//-- Arrange
sut._recorder.counter.Synchron = 0;
var count = 0;
// Actor is recorder-graver
actor = new Recorder.Recorder<stRecorder, PlainstRecorder>(sut._recorder, RecorderModeEnum.Graver).Actor;
sut.Run(() => sut.RunWithRecorder(), // Actual testing method.
() =>
{
Assert.AreEqual(count++, sut._recorder.counter.Synchron);
sut._recorder.counter.Synchron++; // this line changes the state of plc variable for simulation
return sut._recorder.counter.Synchron > 100;
},
null,
null,
actor,
Path.Combine(Runner.RecordingsShell, $"{nameof(RecordAndReplayTest)}.json")
);
// We run the same code with replay.
// Actor is player
actor = new Recorder.Recorder<stRecorder, PlainstRecorder>(sut._recorder, RecorderModeEnum.Player).Actor;
//-- Arrange
sut._recorder.counter.Synchron = 0;
count = 0;
sut.Run(() => sut.RunWithRecorder(), // Actual testing method.
() =>
{
Assert.AreEqual(count++, sut._recorder.counter.Synchron);
// sut._recorder.counter.Synchron++; // this line changes the state of plc variable for simulation commented out in replay.
return sut._recorder.counter.Synchron > 100;
},
null,
null,
actor,
Path.Combine(Runner.RecordingsShell, $"{nameof(RecordAndReplayTest)}.json")
);
}
- Direct use of well-evolved unit testing frameworks in plc code testing.
- Runners are in control of the cycle execution. It allows creating assertions in single cycles.
- Ability to record the state of the plc structure for later reconstruction of hardware behaviour. This is particularly useful when the hardware component is available for testing for a limited time.
- The method is executed by runners and not plc task therefore, it must be taken into consideration the interaction between hard-real-time and non-real-time, in particular when interacting with I/O systems. The units under tests should not be called from real-time, but the execution must be handled from the non-real-time environment. I/O should be mirrored into data transfer variables/objects.
- Whenever the fast execution in order of
us
or low jitter is required, this approach would be is not suitable. - When the execution is provided by test runners of breakpoints in plc program are not hit; however, the state of plc program can be observed.