...
- It has to consist of many separate units. Every unit must have clearly defined interaction with other units.
- Every unit needs to have a clear interface it exposes to other units, so that it can be easily mocked
- Every unit should hold as little state as possible — see below(2.1: A side note on state).
- If a unit is hard to test, it probably means it should be split in multiple components. This usually happens when a component starts holding a lot of state and/or has many possible actions and different outcomes — see below(2.1: A side note on state).
- Every public interface method declaration should be preceded by the
TEST_VIRTUAL
macro — see this subsection(2.3: The TEST_VIRTUAL macro).
2.1. A side note on state
...
Sensor dependencies are a different story. Sensor classes are not available at all in testing, so the default constructor won't be able to construct the sensor class unless we provide it one. Therefore, we need to pay special attention when creating a mock for the sensor -- more on this herein (3.4.1: Sensormocks).
We also need to conditionally include this mock class in the .cpp
implementation, as so:
...
So, if the destructor is virtual, it will be virtual in production and the mock class can easily override it - no macros needed. However, in the rare case that it does not need to be virtual (from the standpoint of production code), you can still make it virtual, or use the TEST_VIRTUAL macro. The first option is preferred because there is very little overhead of a virtual destructor for the StateMachine - it only happens after the code finishes executing = never. It is only worth it if you are really writing a component with no inheritance that is created and destroyed a lot. Additionally, if a destructor is not virtual, when it should be, this causes memory leaks because if a component is destroyed in the parent context, child's destructor is not called unless it is virtual, as we've seen in this example.the example just above from (3.2.1: Clarification on virtual functions and destructors).
state.h
Code Block | ||
---|---|---|
| ||
virtual ~State(); // base class destructor, important to be virtual |
...
A sensor mock serves as a full substitute for a sensor class (as described here), so , described in (3.1.1: Sensor dependencies). So its name must be identical to the original class name. The name of the file containing the mock should still start with Mock
and be located under test/
. It also does not extend from the original class, so double-check that their signatures match, as compiling tests won't verify that as is the case with other components.
...
.cpp
implementation as described here.in (3.1.1: Sensor dependencies).3.5 The unit tests for the component
If you forgot, this the (3: An example of a component and tests for it) section describes the expected behavior of Armed
in detail. We will write separate tests for every feature of the component.
...
EXPECT_CALL
is a GoogleTest macro that helps us verify that the method __die__()
was called on mockDC
. This makes sure mockDC
's destructor is called. __die__()
does not actually refer to the destructor, but we defined the destructor of MockDecayingCondition
to call __die__
as shown in this section(3.4: The mock of the component). For more information on EXPECT_CALL
or other GoogleTest and GoogleMock macros, see this subsection (4.1: The EXPECT_CALL macro) or the useful links section at the bottom (6: Useful references and further reading). Usually, googling things will also work.
...
This is a very simple example of what a test should be doing. It sets some expectations on the mocks and then executes our action. We don't even need to verify the expectations manually; it's done by google test in the background when mocks are destructed. That is another reason why we check for mock destruction in every test (i.e. destructor of TestArmed
is called after every test). See the section titled Test that the destructor was called under (3.5.1: Testing setup code).
You may ask yourself now: where is mockSM
defined? Where are mockBuzzer
, mockLogger
, and mockComHandler
defined? mockSM
is declared in StateMachineFixture
and the rest is defined in MockStateMachine.h
. These files already make sure that when Armed
(or anything else) will request the Buzzer
through the StateMachine
, it will get the mock at mockSM->mockBuzzer
. MockStateMachine
also makes sure that all of those mocks are properly destructed and our expectations verified. Your life is so much easier now because you don't have to worry about that; you just extend StateMachineFixture
(or one of its derived classes) and just easily set expectations on mocks.
...
Read more about the EXPECT_CALL
macro and all of its features just below.
4. In-depth explanation of some testing features and special cases for testing
...
Actually, our great team lead Zack put together a shell script, that executes the whole below procedure (-- still, see the error section (5.1: Errors and fixes). It is located at bin/runUnitTests.sh
(bin
is a sibling of pyxida
) You can run it from bin
or from the top-level folder, but not from anywhere else (for example: pyxida
; bash code for that would require some dark magic not even Zack is certified for).
...
Code Block | ||
---|---|---|
| ||
mkdir build cd build cmake .. make runUnitTests.o ./runUnitTests.o |
5.1 Errors and fixes
Cannot find googletest
Uninitialized git submodules
This problem has many different manifestations. They include
cannot find MadgwickAHRS.h
cannot find googletest
A common error when compiling unit tests will be something in terms of cannot find googletest
. That probably means you haven't initialized git submodules, so run git submodule update --init
Error: leaked mock object
As this error tells you, you have a leaked mock object, which was not deleted. There are multiple possible reasons for this:
...
- that destructor is called (you are deleting the component under test in the test)
- that destructor is virtual (you might be deleting the component but it's its destructor isn't virtual so the child one isn't being called)
- you are deleting the dependency in that destructor.
...
If the mocked object is not a direct dependency of the component under test, it is not responsible for deleting it, so you should delete the mock yourself.
4. You used a mock after it has been deleted
Because of how GoogleTest operates under the hood, setting expectations on a deleted mock will not trigger any segfaults, however, GoogleTest will think that the mock wasn't deleted.
Code Block | ||||
---|---|---|---|---|
| ||||
TEST_F(Fixture1, Test1) {
Mock * ourMock = new Mock; // The mock is created (could also be in the test fixture constructor) at address 0x5634d2e8ca736700
EXPECT_CALL(*ourMock, fun()); // We set expectations on the mock
code->run1(); // We hope this calls ourMock->fun();
delete ourMock; // Here, expectations on ourMock are verified and cleanup is performed. GoogleTest is happy.
EXPECT_CALL(*ourMock, fun()); // OH NO! GoogleTest now again thinks 0x5634d2e8ca736700 is still active
} // after all cleanup, GoogleTest will report "Mock leaked at 0x5634d2e8ca736700 used in Fixture1.Test1", despite it was deleted on line 8.
|
Make sure you don't set EXPECT_CALLs after you delete the mock. Sometimes, they can hide in the test destructor, or in a mock destructor.
SIGSEGV: Segmentation fault
...