Test driven development helps produce high quality code that is stable and maintainable. This post will cover getting started with Ceedling and Unity for test driven development in embedded C.
This is an up-and-running type of post. In future posts, we'll cover:
Ceedling is a build tool for C projects targeted at TDD embedded. It includes multiple utilities and provides a convenient command line interface - an extension of Ruby
's rake
build system.
Unity unit test framework for C bundled with Ceedling
In this project we'll do what all self-respecting engineers do first: blink an LED.
First, install Ceedling (instructions).
Next let's create a new project:
$ ceedling new blinky
And of course, initialize our git repository:
$ cd blinky
$ git init . && git add . && git commit -m "Init"
Now that we have a project to work from, let's add our first module. Ceedling provides helpers to create files for new features via a rake
task. Let's do this for our LED:
$ rake module:create[led]
File src/led.c created
File src/led.h created
File test/test_led.c created
Generate Complete
We can now run tests:
$ rake 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...
Running test_led.out...
-----------
TEST OUTPUT
-----------
[test_led.c]
- ""
--------------------
IGNORED TEST SUMMARY
--------------------
[test_led.c]
Test: test_led_NeedToImplement
At line (14): "Need to Implement led"
--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 0
FAILED: 0
IGNORED: 1
Let's create a test plan. We want our LED module to:
Let's start by writing the test for #1. We'll use a PIC microcontroller. One we've recently used at DEB Associates is the pic18f26k80 (datasheet). For the LED to be initialized properly, we need to set the port direction to output. Our PIC has multiple general purpose IO ports. Let's use A0
for this example.
To do this, we need the TRISA
register bit 0 to be set to 0. Let's start by adding our initialize function and a test.
void test_led_Initializes(void)
{
led_Initialize();
TEST_ASSERT_EQUAL(TRISA & 0x01, 0);
}
In src/led.c
:
void led_Initialize(void)
{
}
In src/led.h
:
void led_Initialize(void);
If we run tests right now, the compiler will tell us that TRISA
is not defined. When we compile the code to run on the pic, we will include the appropriate header that defines TRISA
and maps it to the corresponding address in the chip.
So what do we do for testing? We should use mocking for this purpose, and we will in future posts. For now let's define TRISA
to make the compiler happy.
Let's add TRISA to led.h
:
#include "stdint.h"
uint8_t TRISA;
Now when we run our tests, they pass. But the test is a false positive because TRISA happens to be 0. We can't guarantee value of the TRISA at initialization.
Let's use Unity's setUp
to create a precondition for TRISA
so we can be sure our Initialization
function we write actually does its job.
void setUp(void)
{
TRISA = 1;
}
Running our tests again shows a failure.
Great. Now let's write the code to pass the test.
In src/led.c
:
void led_Initialize(void)
{
TRISA = 0;
}
Now running rake test:all
again shows our initialization test is passing!
Next let's turn the LED on. To turn on the LED, we need to set LATA
bit 0 to 1. First let's add LATA
definition to src/led.c
:
#include "stdint.h"
uint8_t TRISA;
uint8_t LATA;
Note: for PIC micros, LATA is used to write output values and PORTA is used to read values. See datasheet for more info. For purposes of this post, we'll use LATA for reading and writing.
Let's write our test:
void test_led_TurnsOn(void)
{
TEST_ASSERT_EQUAL(LATA & 0x01, 0);
led_TurnOn();
TEST_ASSERT_EQUAL(LATA & 0x01, 1);
}
Don't forget to add the new function led_TurnOn()
to led.c
and led.h
to make our compiler happy.
Now that we have a failing test, let's write the code to get it passing. In led.c
:
void led_TurnOn(void)
{
LATA = 1;
}
Now when we run our tests, we see they are passing:
Test 'test_led.c'
-----------------
Generating runner for test_led.c...
Compiling test_led_runner.c...
Compiling test_led.c...
Compiling led.c...
Linking test_led.out...
Running test_led.out...
-----------
TEST OUTPUT
-----------
[test_led.c]
- ""
--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 2
PASSED: 2
FAILED: 0
Let's write our test and add our new function:
void test_led_TurnsOff(void)
{
led_TurnOn();
TEST_ASSERT_EQUAL(LATA & 0x01, 1);
led_TurnOff();
TEST_ASSERT_EQUAL(LATA & 0x01, 0);
}
In src/led.c
:
void led_TurnOff(void)
{
}
In src/led.h
:
void led_TurnOff(void);
Running tests, we should see a failure. Let's write the code to pass the test:
In src/led.c
:
void led_TurnOff(void)
{
LATA = 0;
}
Running again, we should see passing results!
How about reading the state of the LED?
void test_led_ReportsIfItIsOn(void)
{
led_TurnOn();
TEST_ASSERT_EQUAL(led_IsOn(), 1);
led_TurnOff();
TEST_ASSERT_EQUAL(led_IsOn(), 0);
}
In src/led.c
:
uint8_t led_IsOn(void)
{
return 0;
}
In src/led.h
:
uint8_t led_IsOn(void);
By now this is probably feeling repetitive to read. With our failing test we can implement the code for this feature.
In src/led.c
:
uint8_t led_IsOn(void)
{
return (LATA & 0x01);
}
In this tutorial we set up a project with Ceedling and Unity and got up and running with TDD. There's a lot of functionality that these tools provide - this is just scratching the surface.
There's also a lot more to TDD for embedded systems. I recommend Test-Driven Development for Embedded C by James W. Grenning. It is a well written resource on the subject.
Topics for future posts:
Somthing not right? Let us know!
Dan is an engineer with a background in software development. He graduated from GVSU with BSEE in 2011 and has been building ever since. His tools of choice are C, JavaScript, Elixir and everything that AWS offers.
DEB Associates helps companies through the entire electronics design process. Our team is smart, reliable, and deeply knowledgeable about the intricacies of circuit design, PCB layout, and EMC.