Writing Unit Tests Part 2: Completing the Stub & Running the Test
In this Section
In this section of the tutorial, you will fill in the stub implementation you created in the last section and run the unit test.
Create a Helper Function
Write a generic helper function so you can reuse code while writing unit tests.
Start by writing a function signature in MathSenderTester.hpp
in MathSender/test/ut
:
// In: MathSenderTester.hpp
void testDoMath(MathOp op);
Fill out the corresponding function body in MathSenderTester.cpp
:
// In: MathSenderTester.cpp
void MathSenderTester ::
testDoMath(MathOp op)
{
// Pick values
const F32 val1 = 2.0;
const F32 val2 = 3.0;
// Send the command
// pick a command sequence number
const U32 cmdSeq = 10;
// send DO_MATH command
this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2);
// retrieve the message from the message queue and dispatch the command to the handler
this->component.doDispatch();
// Verify command receipt and response
// verify command response was sent
ASSERT_CMD_RESPONSE_SIZE(1);
// verify the command response was correct as expected
ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK);
// Verify operation request on mathOpOut
// verify that one output port was invoked overall
ASSERT_FROM_PORT_HISTORY_SIZE(1);
// verify that the math operation port was invoked once
ASSERT_from_mathOpOut_SIZE(1);
// verify the arguments of the operation port
ASSERT_from_mathOpOut(0, val1, op, val2);
// Verify telemetry
// verify that 3 channels were written
ASSERT_TLM_SIZE(3);
// verify that the desired telemetry values were sent once
ASSERT_TLM_VAL1_SIZE(1);
ASSERT_TLM_VAL2_SIZE(1);
ASSERT_TLM_OP_SIZE(1);
// verify that the correct telemetry values were sent
ASSERT_TLM_VAL1(0, val1);
ASSERT_TLM_VAL2(0, val2);
ASSERT_TLM_OP(0, op);
// Verify event reports
// verify that one event was sent
ASSERT_EVENTS_SIZE(1);
// verify the expected event was sent once
ASSERT_EVENTS_COMMAND_RECV_SIZE(1);
// verify the correct event arguments were sent
ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2);
}
Explanation
This function is parameterized over different operations.
It is divided into five sections: sending the command, checking the command response, checking the output on mathOpOut
, checking telemetry, and checking events.
The comments explain what is happening in each section. For further information about the F Prime unit test interface, see the F Prime User’s Guide.
Notice that after sending the command to the component, we call the function doDispatch
on the component. We do this in order to simulate the behavior of the active component in a unit test environment.
In a flight configuration, the component has its own thread, and the thread blocks on the doDispatch
call until another thread puts a message on the queue.
In a unit test context, there is only one thread, so the pattern is to place work on the queue and then call doDispatch
on the same thread.
There are a couple of pitfalls to watch out for with this pattern:
-
If you put work on the queue and forget to call
doDispatch
, the work won’t get dispatched. Likely this will cause a unit test failure. -
If you call
doDispatch
without putting work on the queue, the unit test will block until you kill the process (e.g., with control-C).
Write a Function to Test ADD
You will now create a function to test the ADD
command. Add a function signature to MathSenderTester.hpp:
// In: MathSenderTester.hpp
void testAddCommand();
Write the corresponding tester function using the helper function you just wrote:
// In: MathSenderTester.cpp
void MathSenderTester ::
testAddCommand()
{
this->testDoMath(MathOp::ADD);
}
Write a Google test macro in MathSenderTestMain.cpp and make sure the test macro goes before main:
// In: MathSenderTestMain.cpp
TEST(Nominal, AddCommand) {
MathModule::MathSenderTester tester;
tester.testAddCommand();
}
Explanation
The TEST
macro is an instruction to Google Test to run a test. Without this step, your tests will never run. Nominal
is the name of a test suite. We put this test in the Nominal
suite because it addresses nominal (expected) behavior. AddCommand
is the name of the test.
Inside the body of the macro, the first line declares a new object tester
of type MathSenderTester
. We typically declare a new object for each unit test, so that each test starts in a fresh state. The second line invokes the function testAddCommand
that we wrote in the previous section.
Run Your Tests
Run the test you have written. Make sure to execute the following in MathSender
.
# In: MathSender
fprime-util check
As an exercise, try the following:
-
Change the behavior of the component so that it does something incorrect. For example, try adding one to a telemetry value before emitting it.
-
Rerun the test and observe what happens.
Add more command tests
Try to follow the pattern given in the previous section to add three more tests, one each for operations SUB
, MUL
, and DIV
. Most of the work should be done in the helper that we already wrote. Each new test requires just a short test function and a short test macro.
Run the tests to make sure everything compiles and the tests pass.
Summary
In this section you filled out your unit test implementation stub and ran your unit test.