search the lab

Embedded Testing with Unity & CMock (by Mark)

Embedded Testing with Unity & CMock

James Grenning's book (features Unity)

Test Driven Development for Embedded C

Topics

Entries in intro (5)

Sunday
Mar132011

Basic Mocking with CMock

Once you understand how basic unit testing with Unity works, you're ready to understand how CMock can be added to make it extra-powerful.

For each test, one (or more rarely multiple) real module is linked with mock versions of any other modules that it interacts with. These are tested against a test file. Then, another module is tested, bringing in the mocks of files that IT interacts with, and using the real version of it. This continues until all real modules have been tested, and very likely the mock versions of many of them have been used to help.

Here's a pretty picture that helps describe that:

So what are mocks for? Well, they simplify your unit test-writing by allowing you complete control over nearby modules without you have to write a bunch of stubs yourself. They do a number of things for you.

They're Call-Verifiers and Return-Value-Injectors

Let's say you have a function called OpenBarrel. When you call OpenBarrel, it checks for up to five possible monkeys that might be in the Barrel by calling CheckForMonkey(). If the monkey is there, it calls JumpMonkey() so that monkey can leap out joyfully! Let's say OpenBarrel is in Barrel.c and the other two functions are in Monkey.c. We might have a test that looks like this, then:

TestBarrel.c:

#include "unity.h"
#include "cmock.h"
#include "Barrel.h"
#include "MockMonkey.h"

void test_PlayWithMyBarrelOfMonkies(void)
{
    CheckForMonkey_ExpectAndReturn(1); 
    JumpMonkey_Expect();
    CheckForMonkey_ExpectAndReturn(1); 
    JumpMonkey_Expect();
    CheckForMonkey_ExpectAndReturn(0); 

    OpenBarrel();
}

So CMock uses Monkey.h to generate MockMonkey.c and MockMonkey.h. These include mock versions of the original functions, as well as handy helper functions which are used to tell the system what you expect to happen. Then, when you build TestBarrel's executable, you link in this mock version instead of the real Monkey module. See how it's suddenly easier to test Barrel.c? This required no changes to Barrel.c itself... and it didn't require you to hand-create anything for the mocks... you just get auto-generated goodness that you can start to use.

They're Argument-Verifiers

It's kinda hard to check what's going on inside a function sometimes... especially if that function doesn't have any arguments or return values of its own. With mocks, though, this is a piece of cake. When you set up your expectations, you're stating what you expect to get passed.

Let's say I want to peek in the barrel. The first time I want to just check for a crazy monkey by calling CheckForMonkeys(bool crazy_only). The next time I want to check for any monkeys... Then I check for the crazy one again.

TestBarrel.c:

#include "unity.h"
#include "cmock.h"
#include "Barrel.h"
#include "MockMonkey.h"

void test_PeekAtMyMonkeys(void)
{
    CheckForMonkeys_Expect(1);
    CheckForMonkeys_Expect(0);
    CheckForMonkeys_Expect(1);

    PeekInBarrel();
}

Nice, eh? And you can do that for all sorts of arguments... arrays, strings, pointers, structs, numbers... some of those things get a little more complicated, but we're discussing the basics right now, right?

That seems like enough for a basic mocking lesson. There will definitely be more later!

More Mocking Topics

Wednesday
Feb162011

Ceedling Intro

What is Ceedling?

Ceedling is a build system for C projects that is something of an extension around Ruby’s Rake (make-ish) build system. Ceedling is primarily targeted at Test-Driven Development in C and is designed to pull together CMock, Unity, and CException -- three other awesome open-source projects you can’t live without if you're creating awesomeness in the C language. In order to spread the awesomeness around, Ceedling is an extensible contraption with a nice plugin mechanism.

Ceedling is our latest piece of awesomeness geared to pull together all of our C developer goodies into something more cohesive. It is packaged up in a Ruby Gem freely available for mass consumption:

Installing Ceedling

First, you will need to install Ruby (version 1.8.6 or newer. We are enjoying 1.9.2 at the moment).

Ceedling was created to pull together a lot of the magic that duct-taped these individual tools together into a cohesive system. Recently, we also created the Ceedling Ruby Gem which you can grab from RubyGems:

http://rubygems.org/gems/ceedling

The gemification of Ceedling makes getting rolling as easy as typing:

gem install ceedling

If you're going through a firewall (as many people are at work), you may need a slightly more complicated version:

gem install ceedling --http-proxy=http://user:password@server:port

Though Ceedling is officially still in its beta state, it is fully functional and documented as well. Feel free to dig in, and also please feel free to give us feedback in how we can help you and improve the docs. We want to crank the awesomeness up to 11, and your input will help make that happen!

You first need to have the Ruby scripting language installed, and have RubyGems support as well to utilize the gem. Additionally, Ceedling also needs to have some form of GNU GCC tools installed for compilation. Other embedded toolchains are supported as well, but GCC is supported out-of-the box and is also used for dependency generation and preprocessing of files (by default).

To see what you can do with the gem, just call the executable with no arguments:

ceedling

Which will show you the available goodness:

Tasks:
  ceedling example PROJ_NAME [DEST]  # create specified example project (in DEST, if specified)
  ceedling examples                  # list available example projects
  ceedling help [TASK]               # Describe available tasks or one specific task
  ceedling new PROJECT_NAME          # create a new ceedling project
  ceedling update DIRECTORY          # update the vendor/ceedling directory under the given project root
  ceedling version                   # print all ceedling gem and library versions

The Ceedling gem also allows you to generate the skeleton for starting a new project:

ceedling new MyNewProject

You can also generate a sample project that is fully tested in order to have something that just works to refer to:

ceedling example temp_sensor

And finally, you can test your ceedling project as follows (via Rake):

rake test:all

Good luck with Ceedling, and feel free to let us know how your adventures in TDDing embedded C go!

Helpful Links

Wednesday
Feb162011

CMock Intro

What is CMock?

CMock is a module/object mocking framework for C projects useful for interaction-based unit testing. CMock uses Ruby to auto-generate C source code mock object modules conforming to the interfaces specified in C header files. It searches your header files for function declarations

ARGS* ParseStuff(char* Cmd);
void  HandleNeatFeatures(NEAT_FEATURE_T NeatFeature);

and creates a set of Mock objects and helpers

  int  ParseStuff(char* Cmd);
  void ParseStuff_ExpectAndReturn(char* Cmd, int toReturn);
  void ParseStuff_IgnoreAndReturn(int toReturn);
  void ParseStuff_StubAndCallback(CMOCK_ParseStuff_CALLBACK Callback);

  void HandleNeatFeatures(NEAT_FEATURE_T* NeatFeature);
  void HandleNeatFeatures_Expect(NEAT_FEATURE_T* NeatFeature);
  void HandleNeatFeatures_ExpectWithArrays(NEAT_FEATURE_T* NeatFeature, 
                                           int NeatFeature_Depth);
  void HandleNeatFeatures_Ignore(void);
  void HandleNeatFeatures_StubAndCallback(CMOCK_HandleNeatFeatures_CALLBACK
                                          Callback);

which you can use to help write tests

  void test_MyFunc_should_ParseStuffAndCallTheHandlerForNeatFeatures(void)
  {
      NEAT_FEATURES_T ExpectedFeatures = { 1, "NeatStuff" };

      ParseStuff_ExpectAndReturn("NeatStuff", 1);
      HandleNeatFeatures_Expect(ExpectedFeatures);

      //Run Actual Function Under Test
      MyFunc("NeatStuff");
  }

for other modules

  void MyFunc(char* Command)
  {
    int ID;
    NEAT_FEATURES_T Neat;

    ID = ParseStuff(Command);
    switch(ID)
    {
      case 0:  
        HandleStupidFeatures();
        break;
      case 1:
        Neat.id = 1;
        Neat.cmd = Command;
        HandleNeatFeatures(Neat);
        break;
      default:
        break;
    }
  }
Wednesday
Feb162011

CException Intro

What is CException?

CException is simple exception handling in C. It is significantly faster than full-blown C++ exception handling but loses some flexibility. It is portable to any platform supporting setjmp/longjmp.

The main library is only two small files (CException.h and CException.c), plus maybe a config file where you stick 4 defines if you want to customize its behavior. When you download the package, you also get some docs and some tests that we have used to verify that everything is working peachy.

There is also a stub where you can define a function for CException to call when there is no master Try ... Catch... a fallback mechanism so that the application doesn't just take a blind leap into random memory.

There are about a gabillion exception frameworks using a similar setjmp/longjmp method out there... and there will probably be more in the future. Unfortunately, when we started our last embedded project, all those that existed either (a) did not support multiple tasks (therefore multiple stacks) or (b) were way more complex than we really wanted.

Why use CException?

  1. It's ANSI C, and it beats passing error codes around.
  2. You want something simple... CException throws a single id. You can define those ID's to be whatever you like. You might even choose which type that number is for your project. But that's as far as it goes. We weren't interested in passing objects or structs or strings... just simple error codes.
  3. Performance... CException can be configured for single tasking or multitasking. In single tasking, there is very little overhead past the setjmp/longjmp calls (which are already fast). In multitasking, your only overhead is the speed with which you can determine a unique task id.

Is it Reliable?

CException has been tested on a number of platforms (IAR ARM 4, IAR ARM 5, gcc, clang). We're using Unity for a test framework. Also, there are a couple of rules that you need to follow... as long as you stick to the rules, it's very reliable and robust.

Alternatives

Here are a couple of the 'gabillion' exception handling frameworks:

Adam M. Costello's cexcept - Nicely done. Feels a bit more like C++ and allows custom types. If it had existed before I started this project, mine might not exist. Still, it's a bit more complex to use and multitasking is rockier.

Doug Jone's Exception Handling - Small. Different syntax. No multitasking.

Examples

Old surly error handling

BOOL ProductionLine_DoWholeBunchOfStuff( int a )
{
   BOOL retVal = FALSE;

   if (Worker_DoStepA(a) == SUCCESS)
   {
      if (Worker_DoStepB(a+1) == SUCCESS)
      {
         if (Worker_DoStepC(a+2) == SUCCESS)
         {
            retVal = TRUE;
         }
      }
   }

   return retVal;
}

Way better mad scientist error handling

void ProductionLine_DoWholeBunchOfStuff( int a )
{
   CEXCEPTION_T e;

   Try
   {
      Worker_DoStepA(a);
      Worker_DoStepB(a+1);
      Worker_DoStepC(a+2);
   }
   Catch(e)
   {
      SystemLogger_Error(e);
   }
}

void Worker_DoStepA( int a )
{
   if (a < 100)
   {
      Throw( BOOM_GOES_THE_DYNAMITE );
   }

   // do something useful
}

// --- Tests ---

void test_ProductionLine_DoWholeBunchOfStuff( void )
{
   Worker_DoStepA_Expect( 50 );
   Worker_DoStepB_ExpectAndThrow( 51, BOOM_GOES_THE_DYNAMITE );
   SystemLogger_Error_Expect( BOOM_GOES_THE_DYNAMITE );

   ProductionLine_DoWholeBunchOfStuff( 50 );
}

void test_Worker_DoStepA( void )
{
   CEXCEPTION_T e;

   Worker_DoStepA( 101 );
   Worker_DoStepA( 100 );

   Try
   {
      Worker_DoStepA( 99 );
      TEST_FAIL_MESSAGE( "Should have thrown!" );
   }
   Catch(e)
   {
      TEST_ASSERT_EQUAL( BOOM_GOES_THE_DYNAMITE, e );
   }
}
Wednesday
Feb162011

Unity Intro

What is Unity?

Unity is a unit test framework written entirely in the C language. It is lightweight and possesses special features for embedded systems. Unity has scaled well from small to large embedded projects but can be used for any C project. At only one source file and a couple of header files, it doesn’t get much smaller.

  void test_ShowSomeSillyExamples(void)
  {
    TEST_ASSERT_NOT_EQUAL(0, -1);
    TEST_ASSERT_EQUAL_INT(1, 1);
    TEST_ASSERT_EQUAL_HEX16(0x1234, 0x1234);
    TEST_ASSERT_EQUAL_STRING("These Are The Same", "These Are The Same");
    TEST_ASSERT_BITS(0x1111, 0x5555, 0x7175);
    TEST_ASSERT_INT_WITHIN(5, 100, 102);
  }

Links

This page is meant to be a starting point... the launch page for learning more about Unity. If you have any problems, refer to the Science Forums which are for helping with any issues you might run into and requesting new features.