As the title suggests, I was wondering how one goes about testing FBs that have IO variables declared inside of the function block it-self. I am using TcUnit to write tests for a motion library that I made. Right now the only solution I see is to create the outputs from within the test block and link it to the test FB's input that I can't write to from outside. Another option is to use pointers, but I am really not a fan of that, though it might be the only "proper" way to go about this.
Solution using IO linking
Program:
PROGRAM INTERNAL PRG_TESTS
VAR
fbTest : FB_Test;
END_VAR
Test FB:
FUNCTION_BLOCK FB_Test EXTENDS FB_TestSuite
VAR
CantBeModifiedInput AT %I* : WORD;
HackToModifyTheInput AT %Q* : WORD;
END_VAR
TestMethod();
Test method:
METHOD TestMethod
TEST('test');
HackToModifyTheInput := 10;
AssertTrue(CantBeModifiedInput = 10, 'Exepected value to be 10.');
TEST_FINISHED();
This however makes it so that only manual tests can be run, this can't be done automatically on a build server - or can it? I am not at the point of automated tests yet, but it is something to consider for the future.
Solution using direct memory access:
METHOD TestMethod
VAR
testWord : WORD := 10;
END_VAR
//HackToModifyTheInput := 10;
TEST('test');
MEMCPY(
destAddr := ADR(CantBeModifiedInput),
srcAddr := ADR(testWord),
n := SIZEOF(WORD));
AssertTrue(CantBeModifiedInput = 10, 'Exepected value to be 10.');
TEST_FINISHED();
How should I tackle this issue? I really don't want to use inputs and outputs to the fb instead of direct I/O creation from within the block. I feel like the memory manipulation might be the only proper way to be honest.
From the TcUnit FAQ.
In a number of scenarios, TwinCAT won't let you write directly to certain variables:
AT %I*
or AT %Q*
)Writing to these variables wouldn't make sense and should be prevented
in the normal PLC code, so having special privileges during testing is a
must. To support these cases, TcUnit provides helper functions like
WRITE_PROTECTED_BOOL()
, WRITE_PROTECTED_INT()
(and so forth) for
setting these type of variables. For an example of how to use these,
let's assume you have a test:
METHOD PRIVATE TestCommsOkChannelsLow
VAR
EL1008 : FB_Beckhoff_EL1008;
END_VAR
Where the FB_Beckhoff_EL1008
holds a variable:
iChannelInput AT %I* : ARRAY[1..8] OF BOOL;
Now you might want to write a value to the first channel of the
iChannelInput
like:
TcUnit.WRITE_PROTECTED_BOOL(Ptr := ADR(EL1008.iChannelInput[1]),
Value := FALSE);
Whereas afterwards you can make an assertion as usual:
AssertFalse(Condition := EL1008.ChannelInput[1],
Message := 'Channel is not false');
Required TcUnit version: 1.0 or later