Hello everybody
I write mocking tests for testing simple state machine to control electronic pump in my system. I am in first state of machine where i try to mock test several conditions at which pump will be controlled and state machine will transit to another state. Here are some snippets of code with (red comments what I see like problem and want to resolve, and green comments with my idea how to solve it, but perhaps not working solutions)
Simply, I want to mock several functions within state but every test case should only mock (or check) functionality designated to it and ignore other mocks. The problem is these test cases use mixture of the same mock functions. You find more info within comments of code below. I really appreciate any help. I do not want to change signatures of my mock wrappers as they are also part of my valid production code used in comprehensive system which I do not fully have under TDD.
Thank you
Martin ;)
// Some test cases from my mock test fixture
TEST(PumpManage, PumpingStationSwitchOn)
{
FakePump_SetState(Pump_Station_ON_Motor_Off);
const float32 DriveVoltages[] = {24.00, 0.0, 0.0};
mock().setData("DriveVoltages", DriveVoltages);
/* Only this should be my intended mock part of this test case
* testing sequence of commands starting pump station
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", START_PUMP_STATION);
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
*/
/* This is the mock exception part I have to keep to fulfill
* actual mock calls in my teste production code.
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", "SendDataQuery 001,00,313,02=?\n");
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
* However, I don't want them to be here. It is ramble on antipattern */
ManagePump();
}
TEST(PumpManage, PumpMotorSwitchOff)
{
FakePump_SetState(Pump_Station_ON_Motor_Off);
// Sending fake drive voltage values into code under test
const float32 DriveVoltages[] = {24.00, 0.0, 0.0};
mock().setData("DriveVoltages", DriveVoltages);
// The same, this mocking part I do not want to have here
// because this test case is for testing of motor off of pump
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", START_PUMP_STATION);
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
* However, must keep them to fulfill actual mock calls. It's ramble-on antipattern */
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", STOP_PUMP_MOTOR);
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
// Does not work for me, ignores only calls to different functions not to the
// same functions which were already registered. Or am I wrong?
// mock().ignoreOtherCalls();
/* The same as the first commend in this test case
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", "Pump_SendDataQuery 001,00,313,02=?\n");
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
*/
// In manage pump there are calls to actual mock functions. It's my tested production code
ManagePump();
}
TEST(MockPumpManage, PumpDriveVoltageMinimalLimitOn1stReadTransitToConfigState)
{
const float32 DriveVoltages[] = {22.81, 0, 0};
mock().setData("DriveVoltages", DriveVoltages);
FakeHiPace80Pump_SetState(Pump_Station_ON_Motor_Off);
/* This mocking part should not be in this test case as this should test
* pump drive voltage minimum range on first measurement
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", START_PUMP_STATION);
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
* however, I must it keeping here, not to get not fulfilled calls exception errors
*/
// Retrieving drive voltage of pump (24V) param 313
mock().expectOneCall("Execute_Cmd")
.withParameter("mess", "SendDataQuery 001,00,313,02=?\n");
mock().expectOneCall("MsTimer_Wait")
.withParameter("timer", &CpuTimer1)
.withUnsignedIntParameter("timeout", 25)
.andReturnValue(0);
mock().expectOneCall("GetPumpData")
.withPointerParameter("pump", turboPump)
.andReturnValue(FONCTION_OK);
ManagePump();
LONGS_EQUAL(Pump_Station_ON_Motor_Off, PumpSpy_GetPumpLastState());
LONGS_EQUAL(Config_Pump_Station, PumpSpy_GetMachineState());
}
// ManagePump production code under test
void ManagePump(void)
{
uint32 result;
switch(Pump_GetState())
{
case Pump_Station_ON_Motor_Off :
#ifdef TDD_TEST
lastPumpStateSpy = Pump_Station_ON_Motor_Off;
#endif
/* Block with calling actual mock functions
Execute_Cmd(START_PUMP_STATION);
MsTimer_Wait(&CpuTimer1, 25);
GetPumpData(&Pump);
Execute_Cmd(STOP_PUMP_MOTOR);
MsTimer_Wait(&CpuTimer1, 25);
GetPumpData(&Pump);
do
{
Execute_Cmd(GET_DRIVE_VOLTAGE);
MsTimer_Wait(&CpuTimer1, 25);
GetPumpData(&Pump);
#ifdef TDD_MOCK
HiPace80Pump.floatType = ((float32*)mock_c()->getData("DriveVoltages").value.constPointerValue)[Pump.checkDriveVoltageAttempts];
// This it to check whether mock().setData() passed correct fake voltages to
// tested code
printf("iter %u., Pump.floatType = %f\n", Pump.checkDriveVoltageAttempts + 1, Pump.floatType);
#endif TDD_MOCK
Pump.checkDriveVoltageAttempts++;
} while (driveVoltageOutOfRange() && Pump.checkDriveVoltageAttempts < 3);
* block with calling actual mock functions ending here */
if (driveVoltageCorrect())
{
transitToState(Config_Pump_Station);
}
else
{
// ... some code
}
break;
case Config_Pump_Station :
// other states and code ...
}
// Mock actual functions
// Generally, I was thinking to wrap every Execute_Cmd actual mock function wrapper
// into unique function name. E.g.
// uint32 StationOn() calling Execute_Cmd() for pump station on,
// uint32 MotorOff() calling Execute_Cmd() for pump motor off, etc.
// this way I would have individual functions I could call in test cases and functions
// I do not want to call I will ignore with mock().ignoreOtherCalls(). This would work this
// unique wrappers using Execute_Cmd(). However, other mock wrappers MsTimer_Wait
// and GetPumpData, I could not ignore as I need to call the in every test case, however,
// in every test case different number of times. I have feeling that after registering mock // mock expectation, I cannot already ignore it. But, maybe I am wrong :)
uint32 Execute_Cmd(str* mess)
{
// I was thinking about putting every individual Execute_Cmd actual mock call under
// different scope
// e.g. scope "StationOn", "MotorOff", "MeasureVoltage". Then in each test case I could
// only register mock calls dedicated to that test and disable all other scopes.
// the problem is I cannot have more returns from this mock wrapper than one. In this
// case I would need one return for every scope. To resolve that would be very tiresome
// I am not sure, if I can disable only part of mock tests. Something like
// mock_scope_c("StationOn").disable()????
mock_c()->actualCall("Execute_Cmd")
->withStringParameters("mess", mess);
return mock_c()->returnValue().value.unsignedIntValue;
}
int16 MsTimer_Wait(pMsTimer timer, uint32 timeout)
{
mock_c()->actualCall("MsTimer_Wait")->withPointerParameters("timer", timer)
->withUnsignedIntParameters("timeout", timeout);
return mock_c()->intReturnValue();
}
uint32 GetPumpData(pPump pump)
{
mock_c()->actualCall("HiPace80_GetPumpData")
->withPointerParameters("pump", pump);
return mock_c()->unsignedIntReturnValue();
}