Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Replaced jumplinks with section names

...

  • 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 the example just above from (3.2.1: Clarification on virtual functions and destructors).

state.h

Code Block
languagecpp
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.

...

This file will be included by the test and conditionally by the .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).

...