blob: cc107d23a698b30a5339efc27cbfa9056926f40b [file] [log] [blame]
Imre Kis1d2fbdd2019-12-13 11:42:08 +01001Implementing tests
2==================
3
4Concept of unit testing
5-----------------------
6
7First of all unit tests exercise the C code on a function level. The tests
8should call functions directly from the code under tests and verify if their
9return values are matching the expected ones and the functions are behaving
10according to the specification.
11
12Because of the function level testing the dependencies of the tested functions
13should be detached. This is done by mocking the underlying layer. This provides
14an additional advantage of controlling and verifying all the call to the lower
15layer.
16
17
18Adding new unit test suite
19--------------------------
20
21The first step is to define a new unit test suite. If a completely new module is
22being test the test suite definition should be created in a separate ``.cmake``
23file which is placed in the test files' directory. Otherwise the test
24definition can be added to an existing ``.cmake`` file. These files should be
25included in the root ``CMakeLists.txt``.
26
27The ``UnitTest`` CMake module defines the ``unit_test_add_suite`` function so
28before using this function the module must be included in the ``.cmake`` file.
29The function first requires a unique test name which will be test binary's name.
30The test sources, include directories and macro definition are passed to the
31function in the matching arguments. CMake variables can be used to reference
32files relative to common directories:
33
34- ``CMAKE_CURRENT_LIST_DIR`` - Relative to the ``.cmake`` file
35- :cmake:variable:`TF_A_PATH` - Relative to the Trusted Firmware-A root directory
36- :cmake:variable:`TF_A_UNIT_TESTS_PATH` - Relative to the unit test root directory
37
38.. code-block:: cmake
39
40 # tests/new_module/new_test_suite.cmake
41 include(UnitTest)
42
43 unit_test_add_suite(
44 NAME [unique test name]
45 SOURCES
46 [source files]
47 INCLUDE_DIRECTORIES
48 [include directories]
49 COMPILE_DEFINITIONS
50 [defines]
51 )
52
53.. code-block:: cmake
54
55 # Root CMakeLists.txt
56 include(tests/new_module/new_test_suite.cmake)
57
58Example test definition
59^^^^^^^^^^^^^^^^^^^^^^^
60
61.. code-block:: cmake
62
63 unit_test_add_suite(
64 NAME memcmp
65 SOURCES
66 ${CMAKE_CURRENT_LIST_DIR}/test_memcmp.cpp
67 ${CMAKE_CURRENT_LIST_DIR}/memcmp.yml
68 INCLUDE_DIRECTORIES
69 ${TF_A_PATH}/include
70 ${TF_A_PATH}/include/lib/libc/aarch64/
71 )
72
73
74Using c-picker
75--------------
76
77c-picker is a simple tool used for detaching dependencies of the code under
78test. It can copy elements (i.e. functions, variables, etc.) from the original
79source code into generated files. This way the developer can pick functions from
80compilation units and surround them with a mocked environment.
81
82If a ``.yml`` file listed among source files the build system invokes c-picker
83and the generated ``.c`` file is implicitly added to the source file list.
84
85Example .yml file
86^^^^^^^^^^^^^^^^^
87
88In this simple example c-picker is instructed to copy the include directives and
89the ``memcmp`` function from the ``lib/libc/memcmp.c`` file. The root directory
90of the source files referenced by c-picker is the Trusted Firmware-A root
91directory.
92
93.. code-block:: yaml
94
95 elements:
96 - file: lib/libc/memcmp.c
97 type: include
98 - file: lib/libc/memcmp.c
99 type: function
100 name: memcmp
101
102
103Writing unit tests
104------------------
105
106Unit test code should be placed in ``.cpp`` files.
107
108Four-phase test pattern
109^^^^^^^^^^^^^^^^^^^^^^^
110
111All tests cases should follow the four-phase test pattern. This consists of four
112simple steps that altogether ensure the isolation between test cases. These
113steps follows below.
114
115- Setup
116- Exercise
117- Verify
118- Teardown
119
120After the teardown step all global states should be the same as they were at the
121beginning of the setup step.
122
123CppUTest
124^^^^^^^^
125
126CppUTest is an open source unit testing framework for C/C++. It is written in
127C++ so all the useful features of the language is available while testing. It
128automatically collects and runs the defined ``TEST_GROUPS`` and provides an
129interface for implementing the four-phase test pattern. Furthermore the
130framework has assertion macros for many variable types and test scenarios.
131
132Include
133'''''''
134
135The unit test source files should include the CppUTest header after all other
136headers to avoid conflicts.
137
138.. code-block:: C++
139
140 // Other headers
141 // [...]
142
143 #include "CppUTest/TestHarness.h"
144
145Test group
146''''''''''
147
148The next step is to define a test group. When multiple tests cases are written
149around testing the same function or couple related functions these tests cases
150should be part of the same test group. Basically test cases in a test group
151share have same setup/teardown sequence. In CppUTest the ``TEST_GROUP`` macro
152defines a new class which can contain member variables and functions. Special
153setup/teardown function are defined using ``TEST_SETUP`` and ``TEST_TEARDOWN``
154macros. These functions are called before/after running each test case of the
155group so all the common initilization and cleanup code should go into these
156functions.
157
158.. code-block:: C++
159
160 TEST_GROUP(List) {
161 TEST_SETUP() {
162 list = list_alloc();
163 }
164
165 TEST_TEARDOWN() {
166 list_cleanup(list);
167 }
168
169 bool has_element(int value) {
170 for (int i = 0; i < list_count (list); i++) {
171 if (list_get(i) == value) { return true; }
172 }
173 return false;
174 }
175
176 List* list;
177 };
178
179
180Test case
181'''''''''
182
183Test cases are defined by the ``TEST`` macro. This macro defines a child class
184of the test group's class so it can access the member functions and variables of
185the test group. The test case's block itself is the body of the function of the
186child class.
187
188.. code-block:: C++
189
190 TEST(List, add_one) {
191 // Exercise
192 const int test_value = 5;
193 list_add(list, test_value);
194
195 // Verify using CHECK_TRUE assertion and TEST_GROUP member function
196 CHECK_TRUE(has_element(test_value));
197 }
198
199 TEST(List, add_two) {
200 // Exercise
201 const int test_value1 = 5;
202 const int test_value2 = 6;
203 list_add(list, test_value1);
204 list_add(list, test_value2);
205
206 // Verify
207 CHECK_TRUE(has_element(test_value1));
208 CHECK_TRUE(has_element(test_value2));
209 }
210
211CppUMock
212^^^^^^^^
213
214During unit testing the dependencies of the tested functions should be replaced
215by stubs or mocks. When using mocks the developer would usually like to check if
216the function was called with corrent parameters and would like to return
217controlled values from the function. When a mocked function is called multiple
218times from the tested function maybe it should check or return different values
219on each call. This is where CppUMock comes handy.
220
221All CppUMock interactions start with calling the ``mock()`` function. This
222returs a reference to the mocking system. At this point the developer either
223wants to define expected or actual calls. This is achiveable by calling
224``expectOneCall(functionName)`` or ``expectNCalls(amount, functionName)`` or
225``actualCall(functionName)`` member functions of ``mock()`` call's return value.
226Registering expected calls are done in the test case before exercising the code
227and actual calls happen from the mocked functions.
228
229After this point the following functions can be chained:
230
231- ``onObject(object)`` - In C++ it is usually the ``this`` pointer but it can be
232 useful in C too.
233- ``with[type]Parameter(name, value)`` - Specifying and checking call parameters
234- ``andReturnValue(result)`` - Specifying return value when defining expected
235 call
236- ``return[type]Value()`` - Returning value from function
237
238The mocking system has two important functions. ``mock().checkExpectation()``
239checks if all the expected calls have been fulfilled and and the
240``mock().clear()`` removes all the expected calls from CppUMock's registry.
241These two functions are usually called from the ``TEST_TEARDOWN`` function
242because there should not be any crosstalk between test cases through the mocking
243system.
244
245CppUMock's typical use-case is shown below by a simple example of the
246``print_to_eeprom`` function.
247
248.. code-block:: C++
249
250 int eeprom_write(const char* str); /* From eeprom.h */
251
252 int printf_to_eeprom(const char* format, ...) {
253 char buffer[256];
254 int length, written_bytes = 0, eeprom_write_result;
255 va_list args;
256
257 va_start(args, format);
258 length = vsnprintf(buffer, sizeof(buffer), format, args);
259 va_end(args);
260
261 if (length < 0) {
262 return length;
263 }
264
265 while(written_bytes < length) {
266 eeprom_write_result = eeprom_write(&buffer[written_bytes]);
267 if (eeprom_write_result < 0) {
268 return eeprom_write_result;
269 }
270 written_bytes += eeprom_write_result;
271 }
272
273 return written_bytes;
274 }
275
276Having the code snipped above a real life usage of the function would look like
277something shown in the following sequence diagram.
278
279.. uml:: resources/sequence_print_to_eeprom.puml
280
281It would be really hard to test unit this whole system so all the lower layers
282should be separated and mock on the first possible level. In the following
283example the ``print_to_eeprom`` function is being tested and the
284``eeprom_write`` function is mocked. In test cases where ``eeprom_write``
285function is expected to be called the test case should first call the
286``expect_write`` function. This registers an expected call to CppUMocks internal
287database and when the call actually happens it matches the call parameters with
288the entry in the database. It also returns the previously specified value.
289
290.. code-block:: C++
291
292 TEST_GROUP(printf_to_eeprom) {
293 TEST_TEARDOWN() {
294 mock().checkExpectations();
295 mock().clear();
296 }
297
298 void expect_write(const char* str, int result) {
299 mock().expectOneCall("eeprom_write").withStringParameter("str", str).
300 andReturnValue(result);
301 }
302 };
303
304 /* Mocked function */
305 int eeprom_write(const char* str) {
306 return mock().actualCall("eeprom_write").withStringParameter("str", str).
307 returnIntValue();
308 }
309
310 TEST(printf_to_eeprom, empty) {
311 LONGS_EQUAL(0, printf_to_eeprom(""))
312 }
313
314 TEST(printf_to_eeprom, two_writes) {
315 expect_write("hello1hello2", 6);
316 expect_write("hello2", 6);
317 LONGS_EQUAL(12, printf_to_eeprom("hello%dhello%d", 1, 2))
318 }
319
320 TEST(printf_to_eeprom, error) {
321 expect_write("hello", -1);
322 LONGS_EQUAL(-1, printf_to_eeprom("hello"))
323 }
324
325This how the ``printf_to_eeprom/two_writes`` test case's sequence diagram looks
326like after mocking ``eeprom_write``. The test case became able to check the
327parameters of multiple calls and it could return controlled values.
328
329.. uml:: resources/sequence_print_to_eeprom_mock.puml
330
331
332Analyzing code coverage
333-----------------------
334
335The code coverage reports can be easily used for finding untested parts of the
336code. The two main parts of the coverage report are the line coverage and the
337branch coverage. Line coverage shows that how many times the tests ran the given
338line of the source code. It is beneficial to increase the line coverage however
339100% line coverage is still not enough to consider the code fully tested.
340
341Let's have a look on the following example.
342
343.. code-block:: C++
344
345 void set_pointer_value(unsigned int id, unsigned int value) {
346 unsigned int *pointer;
347
348 if (id < MAX_ID) {
349 pointer = get_pointer(id);
350 }
351
352 *pointer = value;
353 }
354
355The 100% line coverage is achievable by testing the function with an ``id``
356value smaller than ``MAX_ID``. However if an ``id`` larger than or equal to
357``MAX_ID`` is used as a parameter of this function it will try to write to a
358memory address pointed by an uninitialized variable. To catch untested
359conditions like this the branch coverage comes handy. It will show that only one
360branch of the ``if`` statement has been tested as the condition was always true
361in the tests.
362
363
364--------------
365
366*Copyright (c) 2019-2020, Arm Limited. All rights reserved.*