You have a function returning a value that depends on the datetime. If you implement this without Dependency Injection, the test results depend on the current time.
In the below example, it is a coffee machine that tells you if it's time for a coffee or not.
CLASS CoffeeMachine
METHOD PUBLIC IsItCoffeeTime : BOOL
VAR_TEMP
currentTime : TIME_OF_DAY;
hour : INT;
systemTime : LDATE_AND_TIME;
END_VAR
// This line is responsible for the timing problem
Siemens.Simatic.S71500.Clocks.GetSystemDateTime(systemTime);
// Here we get only the time from the datetime
SplitDateAndTime(
value := systemTime,
hour => hour
);
//the machine thinks it's unhealthy to drink coffee
IF hour > 6 AND hour < 20 THEN
IsItCoffeeTime := TRUE;
ELSE
IsItCoffeeTime := FALSE;
END_IF;
END_METHOD
END_CLASS
A test for this function could look like this:
{TestFixture}
CLASS CoffeeMachineTests
VAR PROTECTED
_coffeeMachine : CoffeeMachine;
END_VAR
// The result of this test varies dependent of the time when you start it
{Test}
METHOD PUBLIC IsItCoffeeTimeReturnsTrueWhenBetween6And20
VAR_TEMP
result : BOOL;
END_VAR
result := _coffeeMachine.IsItCoffeeTime();
Assert.Equal(actual := result, expected := TRUE);
END_METHOD
END_CLASS
If you want to be independent of the current time during testing, you have to be in control of it. Especially, if this is a static function (like here) the problem gets even worse.
To overcome the problem, you have to introduce an abstraction layer around the time function and inject this into your coffeeMachine.
In the following example, we define an interface which declares a function that returns the current time. A class is implementing that interface and returns the current time from the system function. Now the coffeeMachine is not dependent on the static system time function anymore.
// this interface defines a behavior of a class implementing it
INTERFACE PUBLIC ISystemFunctions
METHOD GetSystemDateAndTime : LDATE_AND_TIME
END_METHOD
END_INTERFACE
// this class uses the above interface and encapsulates the dependency
// to the static GetSystemDataTime function
CLASS PUBLIC SystemFunctionsImpl IMPLEMENTS ISystemFunctions
METHOD PUBLIC GetSystemDateAndTime : LDATE_AND_TIME
VAR_TEMP
systemTime : LDATE_AND_TIME;
END_VAR
Siemens.Simatic.S71500.Clocks.GetSystemDateTime(systemTime);
GetSystemDateAndTime := systemTime;
END_METHOD
END_CLASS
// this implementation of the coffee machine gets the instance of the class,
// implementing ISystemFunctions from the outside.
CLASS CoffeeMachine
VAR PUBLIC
SystemFunctions : ISystemFunctions;
END_VAR
METHOD PUBLIC IsItCoffeeTime : BOOL
VAR_TEMP
currentTime : TIME_OF_DAY;
hour : INT;
END_VAR
SplitDateAndTime(
value := SystemFunctions.GetSystemDateAndTime(),
hour => hour
);
IF hour > 6 AND hour < 20 THEN
IsItCoffeeTime := TRUE;
ELSE
IsItCoffeeTime := FALSE;
END_IF;
END_METHOD
END_CLASS
Additionally, you have to implement a second class implementing that interface. This class will be used during testing and is able to return the values you define by setting SetSystemDateAndTime:
CLASS PUBLIC SystemFunctionsMock IMPLEMENTS ISystemFunctions
VAR PUBLIC
SetSystemDateAndTime : LDATE_AND_TIME;
END_VAR
METHOD PUBLIC GetSystemDateAndTime : LDATE_AND_TIME
GetSystemDateAndTime := SetSystemDateAndTime;
END_METHOD
END_CLASS
Now you can write your test like that:
{TestFixture}
CLASS CoffeeMachineTests
VAR PROTECTED
_systemFnMock : SystemFunctionsMock;
_coffeeMachine : CoffeeMachine;
END_VAR
//Example of how to write a test for the CoffeeMachine, where we dictate the time
{Test}
METHOD PUBLIC IsItCoffeeTimeReturnsTrueWhenBetween6And20
VAR_TEMP
result : BOOL;
END_VAR
//Setup -> set the desired testing time, instead of the real time, than give the mock to the class
_systemFnMock.SetSystemDateAndTime := LDT#1980-01-23-13:14:33.123456;
_coffeeMachine.SystemFunctions := _systemFnMock;
//Test
result := _coffeeMachine.IsItCoffeeTime();
Assert.Equal(actual := result, expected := TRUE);
END_METHOD
END_CLASS
Now you can execute the test and control the time which is used for the calculation.