testingtwincatstructured-text

Writing tests for FBs with IO variables within


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();

enter image description here

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.


Solution

  • From the TcUnit FAQ.

    In a number of scenarios, TwinCAT won't let you write directly to certain variables:

    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