Note that you can have any folder tree that you want below src, so you can move any existing source folders in there as well.

Ceedling Tip: You can confirm that Ceedling knows about your source files by running ceedling files:source :

projects\blinky> ceedling files:source source files: - src/blinky.c - src/led.c file count: 2

Yeah! Now Ceedling is installed in our project and ready to go.

Create a new test file

Now that Ceedling is installed, it's time to add some tests. The LED driver (led.c) is a good candidate here because it's an isolated module. Before we can add tests we'll need a new test file to put the them in.

Ceedling makes it easy to create the test files for existing modules with its module:create command. Typically this command creates a .c, .h and a test file for a new source module. If a file already exists though, the file is left untouched. This means we can use it to easily create a test file for led.c:

projects\blinky> ceedling module:create[led] Generating 'led'... mkdir -p ./test/. mkdir -p ./src/. File ./test/./test_led.c created File ./src/./led.c already exists! File ./src/./led.h already exists!

The test file it created is test/test_led.c. This is built from a template that includes the header files and the setUp() and tearDown() functions needed by any test. This saves us the time of having to manually copy/paste/edit this from another test file.

Getting it to build

Now that we have a test file for our LED module, we need to get it to build. Here's where the real fun begins! In this step we're going to chase down a bunch of errors as we try to find the "seams" of the LED module so that we can test it isolation.

This is going to involve setting up some mocks and configuring Ceedling. We're just going to read the error messages and use them to figure out what needs to be fixed at each stage.

Adding more source folders

After adding our first test file test_led.c, if we try to run the tests we get our first error:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- Generating runner for test_led.c... Compiling test_led_runner.c... Compiling test_led.c... Compiling unity.c... Compiling led.c... src/led.c:5:27: fatal error: inc/hw_memmap.h: No such file or directory #include "inc/hw_memmap.h" ^

If we take a look at led.c it includes a couple files from our processor library: hw_memmap.h and gpio.h. These are driver files provided by TI for controlling the GPIO:

#include "led.h" #include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "driverlib/gpio.h" void led_turn_on(void) { GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2); } void led_turn_off(void) { GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0); }

When I installed TivaWare, these files were installed to C:\ti\TivaWare_C_Series-2.1.2.111 but Ceedling thinks that all of our source files are in the src folder. We need to tell Ceedling how to look in this other folder for source as well.

Ceedling is configured in the project.yml file. This is a YAML file that Ceedling loads each time it is run. In project.yml there is a section for :paths: which includes settings for :test: , :source: , and :support: . You add another source path (like C:\ti\TivaWare_C_Series-2.1.2.111) by adding another path to the :source: list:

:paths: :test: - +:test/** - -:test/support :source: - src/** - C:\ti\TivaWare_C_Series-2.1.2.111 # This is the new source path. :support: - test/support

Mocking hardware drivers from the header files

With the source path for the TI drivers set, we can try to run Ceedling again:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- Generating runner for test_led.c... Compiling test_led_runner.c... Compiling test_led.c... Compiling unity.c... Compiling led.c... Compiling cmock.c... Linking test_led.out... build/test/out/led.o: In function `led_turn_on': projects/blinky/src/led.c:10: undefined reference to `GPIOPinWrite'

Now that Ceedling can find driverlib/gpio.h, it knows that it needs to link in a GPIOPinWrite function. Now, we're not going use the real function since we're running on the host PC. So we need to mock it. We can mock all of the functions in gpio.h in our test by adding #include "mock_gpio.h" to test_led.c:

#include "unity.h" #include "led.h" #include "mock_gpio.h" // This will mock the functions in driverlib/gpio.h. void setUp(void) { } void tearDown(void) { } void test_module_generator_needs_to_be_implemented(void) { TEST_IGNORE_MESSAGE("Implement me!"); }

CMock won't do paths to header files

Now we're getting somewhere! Let's try running Ceedling again:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- ERROR: Found no file 'gpio.h' in search paths. rake aborted!

Oh, so Ceedling can't find driverlib/gpio.h so that it can mock it. Remember how we included mock_gpio.h in the test? Well Ceedling is looking in all of its configured source folders for gpio.h, not driverlib/gpio.h. We need add the driverlib folder to the source paths so that it can find gpio.h in there:

:paths: :test: - +:test/** - -:test/support :source: - src/** - C:\ti\TivaWare_C_Series-2.1.2.111 - C:\ti\TivaWare_C_Series-2.1.2.111\driverlib # Now we can find gpio.h. :support: - test/support

Including other header files in our mocks

What will the next error be?? Running the tests again gives us this one:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- Creating mock for gpio... WARNING: No function prototypes found! Generating runner for test_led.c... Compiling test_led_runner.c... In file included from build/test/mocks/mock_gpio.h:5:0, from build/test/runners/test_led_runner.c:30: C:/ti/TivaWare_C_Series-2.1.2.111/driverlib/gpio.h:153:50: error: unknown type name 'bool' extern uint32_t GPIOIntStatus(uint32_t ui32Port, bool bMasked);

Okay. So this is a problem with using off-the-shelf code from somewhere else (thank you TI). These TivaWare driver files (like gpio.h) are set up strangely. Even though gpio.h needs stdbool.h and stdint.h it doesn't actually #include them. As the user, you're supposed to include them in your source file before including gpio.h.

Unfortunately this means we need to include stdbool.h and stdint.h in our auto-generated mock files. Fortunately Ceedling has a setting for this in the :cmock: section of project.yml. We can add an :includes: setting like this:

:cmock: :mock_prefix: mock_ :when_no_prototypes: :warn :enforce_strict_ordering: TRUE :plugins: - :ignore - :callback :treat_as: uint8: HEX8 uint16: HEX16 uint32: UINT32 int8: INT8 bool: UINT8 :includes: # This will add these includes to each mock. - <stdbool.h> - <stdint.h>

Enabling mocks for extern-ed function prototypes.

With those include files added, let's run Ceedling again and get our next error:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- Creating mock for gpio... WARNING: No function prototypes found! Generating runner for test_led.c... Compiling test_led_runner.c... Compiling test_led.c... Compiling mock_gpio.c... Compiling unity.c... Compiling led.c... Compiling cmock.c... Linking test_led.out... build/test/out/led.o: In function `led_turn_on': projects/blinky/src/led.c:10: undefined reference to `GPIOPinWrite'

Hmmm... we can't find GPIOPinWrite again. If we take a closer look, we can see a WARNING: No function prototypes found! message when trying to create the mock for gpio.h. Again, this Tiva library is strange -- this time because all the function prototypes in the header files are extern-ed:

extern void GPIOPinWrite(uint32_t ui32Port, uint8_t ui8Pins, uint8_t ui8Val);

By default Ceedling/CMock won't mock functions labeled extern . We need to tell CMock to mock these functions by adding the :treat_externs: setting:

:cmock: :mock_prefix: mock_ :when_no_prototypes: :warn :enforce_strict_ordering: TRUE :plugins: - :ignore - :callback :treat_as: uint8: HEX8 uint16: HEX16 uint32: UINT32 int8: INT8 bool: UINT8 :includes: - <stdbool.h> - <stdint.h> :treat_externs: :include # Now the extern-ed functions will be mocked.

Hooray! Now our test will finally build, and we can actually run the tests:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- Creating mock for gpio... Generating runner for test_led.c... Compiling test_led_runner.c... Compiling test_led.c... Compiling mock_gpio.c... Compiling unity.c... Compiling led.c... Compiling cmock.c... Linking test_led.out... Running test_led.out... ----------- TEST OUTPUT ----------- [test_led.c] - "" -------------------- IGNORED TEST SUMMARY -------------------- [test_led.c] Test: test_module_generator_needs_to_be_implemented At line (16): "Implement me!" -------------------- OVERALL TEST SUMMARY -------------------- TESTED: 1 PASSED: 0 FAILED: 0 IGNORED: 1

Add an actual unit test

Setup and configuration is always a difficult part for embedded projects. Now though we've managed to fight through it... so that we can get down to the business of actually writing some unit tests.

Since the LED on our board is connectted to pin 2 of port F, we might want to test that our led_turn_on function uses GPIOPinWrite to set pin 2 of port F. We can create a new unit test function in test/test_led.c and use an expectation to do this:

include "inc/hw_memmap.h" void test_when_the_led_is_turned_on_then_port_f_pin_2_is_set(void) { // Expect PORTF pin 2 to be set. GPIOPinWrite_Expect(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2); // Call the function under test. led_turn_on(); }

Note that we needed to #include "inc/hw_memmap.h" to get access to GPIO_PORTF_BASE , and GPIO_PIN_2 .

And if we run our tests now, we can watch it pass:

projects\blinky> ceedling test:all Test 'test_led.c' ----------------- Generating runner for test_led.c... Compiling test_led_runner.c... Compiling test_led.c... Linking test_led.out... Running test_led.out... ----------- TEST OUTPUT ----------- [test_led.c] - "" -------------------- OVERALL TEST SUMMARY -------------------- TESTED: 1 PASSED: 1 FAILED: 0 IGNORED: 0

The next steps

Now that you have a unit test framework set up for your project, your ready to start incrementally adding tests where you can -- gradually make your embedded software better.

You should consider trying to have tests for any new code you're writing, but do what you can. Are you chasing a bug? See if you can create a failing test for it first. Then make it pass. Bam, bug fixed!

Also -- to make it easier to run the tests -- you could set up your IDE to run ceedling test:all when you press a keyboard shortcut. Better yet, you could set it up to run ceedling test:<your_current_file> (with the current file in your editor). This only runs the tests for the file that your working on. Eventually when you have many more tests, this will be a lot faster.

Get the source code for this complete example on GitHub.