I've recently tested a FAT file system and bootloader implementation with gTest (GoogleTest) for an Arm Cortex-M3 core, so I'll leave my two cents.
Embedded software testing presents the problem that it's impossible to replicate the HW environment through mocking. I came up with three sets of tests:
A) Unit tests (that I use in TDD) that run on my PC. I use these tests to develop my application logic. This is where I need mocking/stubbing. My company uses a hardware abstraction layer (HAL), and that's what I mock. This last bit is fundamental if you want to write testable code.
/* this is not testable */
my_register->bit0 = 1;
/* this is also not testable */
*my_register |= BIT0;
Don't do direct register access, use a simple HAL wrapper function that can be mocked:
/* this is testable */
void set_bit(uint32_t* reg, uint8_t bit)
{
*reg |= bit;
}
set_bit(my_register , BIT0);
The latter is testable because you're going to mock the set_bit
function, thus breaking the dependency on the HW.
B) Unit tests on the target. This is a much smaller set of tests than (A), but it's still useful especially for testing drivers and the HAL functions. The idea behind these tests is that I can properly test the functions that I'll mock. Because this runs on the target, I need this as simple and lightweight as possible, so I use MinUnit, which is a single C header file. I've run on-target tests with MinUnit on a Cortex-M3 core and on a proprietary DSP code (without any modifications). I've also used TDD here.
C) Integration tests. I use Python and Behave here, to build, download, and run the whole application on the target.
Answering your questions:
As others have already said, start with gTest Primer, and don't worry about mocking, just getting the hang of using gTest. A good alternative that offers some memory checking (for leaks) is Cpputest. I have a slight preference for the gTest syntax for deriving the setup classes. Cpputest can run tests written with gTest. Both are great frameworks.
I used Fake Function Frakework for mocking and stubbing. It's ridiculously simple to use and it offers everything expected from a good mocking framework: setting different return values, passing callbacks, checking the argument call history, etc. I want to give Ceedling a go. So far FFF has been great.
I don't do that. I compile the testing framework and my tests with a C++ compiler (g++ in my case) and my embedded code with a C compiler (gcc), and just link them together. From the example below, you'll see that I don't include C++ headers in C files. When linking your tests, you'll link everything but the C source files for the functions you're mocking.
Managing failed assertions with the code - Failed assertions within my driver library, calls for a system reset. How can I emulate this within the tests?
I'd mock the reset function, adding a callback to "reset" whatever you need.
Say that you want to test a read_temperature
function that uses the read
function. Below is a gTest example that uses FFF for mocking.
hal_i2c.h
/* HAL Low-level driver function */
/**
* @brief Read a byte from the I2C bus.
*
* @param address The I2C slave address.
* @return uint8_t The data read from the I2C bus.
*/
uint8_t read(uint8_t address);
read_temperature.h
/**
* @brief Read the temperature from the sensor in the board
*
* @return float The temperature in degrees Celsius.
*/
float read_temperature(void);
read_temp.c
#include <hal_i2c.h>
float read_temperature(void)
{
unit8_t raw_value;
float temp;
/* Read the raw value from the I2C sensor at address 0xAB */
raw_value = read(0xAB);
/* Convert the raw value to Celcius */
temp = ((float)raw_value)/0.17+273;
return temp;
}
test_i2c.cpp
#include <gtest/gtest.h>
#include <fff.h>
DEFINE_FFF_GLOBALS;
extern "C"
{
#include <hal_i2c.h>
#include <read_temperature.h>
// Declare the fake C functions using FFF. This needs be inside the extern "C"
// block because we're mocking a C function and the C++ name-mangling would
// break linking otherwise.
// Create a mock for the uint8_t read(uint8_t address) function.
FAKE_VALUE_FUNC(uint8_t , read, uint8_t);
}
TEST(I2CTest, test_read) {
// This clears the FFF counters for the fake read() function
RESET_FAKE(read);
// Set the raw temperature value the fake for read should return
read_fake.return_val = 0xAB;
// Make sure that we read 123.4 degrees
ASSERT_EQ((float)123.4, read_temperature());
}
For more complex test scenarios with test classes, you can call RESET_FAKE
in the SetUp()
method.
Hope this helps! Cheers!