3/21/18 by Dan

Test Driven Development for Embedded Software with Unity and Ceedling

In this post we’ll cover getting started with Ceedling and Unity TDD for embedded.

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.

IMG 6509

This is an up-and-running type of post. In future posts, we'll cover:

  • Compiling and running on a Microchip PIC processor
  • Using Microchip tools in concert with Ceedling/Unity
  • Using CMock for mocking

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.

Setup

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"

Our first module

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

Test plan

Let's create a test plan. We want our LED module to:

  1. Initialize properly
  2. Turn on
  3. Turn off
  4. Read state

Initialization

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!

Turning On

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

Turn off

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!

Read State

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);
}

Summary

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:

  • Compiling and running on a Microchip PIC processor
  • Using Mircochip tools in concert with Ceedling/Unity
  • Using CMock for mocking
  • Using gcov for code coverage reports

Somthing not right? Let us know!

By: Dan


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.

Like what you see?

About Us

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.


DEB Associates 2020