Introduction
This article is about unit testing. Although the unit testing is quite popular in various programming languages, the embedded C programming is usually overlooked. This is ironic because embedded programmer may benefit from the unit testing like no other. If you work with embedded systems then you must know how hard it is to debug your code. You add logs, you connect JTAG emulator, you sweat, you curse... if so, this article was written to help you. Give it a try.
In this article I use GoogletTest and GoogleMock frameworks for unit tests writing. I assume that the reader is familiar with these frameworks. Otherwise you should read GoogleTest and GoogleMock documentation first. While the GoogleTest could be easily adjusted to C testing, the GoogleMock has a little to propose to the C programmer. The GoogleMock framework was designed for mocking C++ interfaces and it relies on the virtual functions mechanics, which is lacking in the C language. Without mocking interfaces, the unit testing becomes very limited. In this article I suggest two different approaches for C functions mocking and then bundle them into one solution that addresses majority of the unit testing scenarios. This article also covers some design aspects to help you in unit tests writing.
The techniques that I describe in this article are not new. Some of them were borrowed from the "Test driven development for embedded C" book. The book is an excellent C unit testing resource but, in my opinion, it should have described in more details how to utilize frameworks for the C functions mocking. There are a lot of articles and frameworks over the Internet trying to address the C mocking problem, but I found them incomplete. Most of them are limited to GNU/Linux environment and provide no practical guidance for unit testing writing. Also, in this article, I propose HOOK mocking technique that I haven't found in any other framework.
Testing C functions with GoogleTest
Let's say we have a simple queue code and we want to put it under test. The code resides in
Queue.c
file.
#define QUEUE_SIZE 15
static
int
queue[QUEUE_SIZE];
static
int
head, tail, count;
void
QueueReset()
head = tail = count =
0
;
int
QueueCount()
return
count;
int
QueuePush(
int
i)
if
(count == QUEUE_SIZE)
return
-
1
;
queue[tail] = i;
tail = (tail +
1
) % QUEUE_SIZE;
count++;
return
0
;
The trick is simple. After creating C++ file for the test, include the C file into it and the C code will become accessible to the test code. Below is the code of
QueueUnitTest.cpp
file.
#include
"
Queue.c"
TEST_F(QueueUnitTest, ResetTest)
EXPECT_EQ(
0
, QueuePush(
1
));
EXPECT_EQ(
1
, QueueCount());
QueueReset();
EXPECT_EQ(
0
, QueueCount());
TEST_F(QueueUnitTest, CountTest)
for
(
auto
i =
0
; i
<
QUEUE_SIZE; i++)
EXPECT_EQ(
0
, QueuePush(i));
EXPECT_EQ(QUEUE_SIZE, QueueCount());
Why not to include
Queue.h
instead of
Queue.c
? Indeed, this will allow you to call the C functions declared in the h file, but you won't get access to the static functions and the static global variables declared in the C file.
Now, let's discuss the global variables. There are no classes in C, but in well designed C program, the code is divided into modules which reside in separated files and the global variables are usually declared to be static. Thus, C module could be considered as C++ class equivalent and module's global variables are similar to the class
private
static
fields. If so, we should be able to initialize them before running a test. Consider adding
ModuleName
Init
method and put the global variables initialization into it. Here is an example of such initialization in
Queue.c
file.
void
QueueInit()
head = tail = count =
0
;
void
QueueReset()
QueueInit();
The test fixture class can now call the
QueueInit
function and prepare the module for its test. The GoogleTest framework creates a new fixture object for every test and, thus, the
QueueInit
function will run before each test execution.
#include
"
Fixture.h"
#include
"
Queue.c"
class
QueueUnitTest :
public
TestFixture
public:
QueueUnitTest()
QueueInit();
TEST_F(QueueUnitTest, ResetTest)
EXPECT_EQ(
0
, QueuePush(
1
));
EXPECT_EQ(
1
, QueueCount());
QueueReset();
EXPECT_EQ(
0
, QueueCount());
Mocks in C code
In previous section we saw how to test the C functions, but what if the function we test calls another function, that resides outside of our module? It could be a log service that our queue should call on push operation or, may be, it is required to signal GPIO if the queue is full. To handle such situations we need to create mock objects and teach the code to call mocks instead of the production code. I found two useful techniques for the C functions mocking and I suggest choosing between them based on the nature of the mocked functions.
Mocking services
The first technique suggests to cut functions off your code and replace them with fake implementations. These fake implementations, in turn, will call to the to the corresponding mock objects. This approach is mostly suitable for hardware and OS services, such as timers, GPIO-s, OS logs, IPC entities and so on. Indeed, these types of services are not related to your application code and should not be put under test. Their fake implementations provide your application with kind of virtual runtime environment.
Let's say we have an oscillator and GPIO registers and we wrap them with the following functions.
int
GetTime()