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 UnityHelper (1)

Sunday
Mar272011

CMock and Custom Types

CMock handles custom types (types that you have created as typedefs or #define aliases) in one of three ways. The first way is the default method, which happens if you don't do anything to point it at one of the other two methods. The other two give you more informative error messages when a mismatch occurs, but are slightly more work.

Memory Comparisons (default behavior)

If CMock comes across a type that it doesn't recognize and you haven't instructed it to treat it differently, it's going to perform a memory comparison between the expected value and the actual value. This works almost all the time (the exception being possibly unpacked structs). The downside to this method is that a failure will just report that there is a mismatch, but it's left completely to the developer to figure out what happened.

Treat As (for things that act like basic types)

Often custom types are enumerations or custom names given to basic types (like U16 for an unsigned short or MY_BYTE_PTR for char*). These are handle most easily by telling CMock to just treat these as a base type that it understands. You can even use this for specifying which format you would like to see your integer types displayed as.

When CMock is instantiated, you either pass it configuration information through a Ruby function call or you configure it in a yaml file. In either case, you have the option to set the :treat_as config option to a hash. You create a key string for every custom type you want to map, and set it's value to the base type you wish to support. The base types are as follows:

Base TypeWhat is it?
INTTreats it as a signed integer. You can also use INT8, INT16, etc.
UINTTreats it as an unsigned integer. You can also use UINT8, UINT16, etc.
HEXTreats it as an unsigned integer, displayed as hex. Again, HEX8, etc. work too
STRINGThe usual char* (null terminate) C string
FLOATTreat it as a floating point number
PTRJust compares the pointers, not the contents pointed to.

A special note about that last one. Comparing the pointer is sometimes useful, but usually you really want to work with the basic type. But it needs to understand that you're dealing with a pointer, right? Let's say you have a type like this:

typedef unsigned int* P_UINT;

You might want to add an entry to :treat_as like this one 
(in Ruby call syntax):

:treat_as => { "P_UINT" => "HEX32*" }

or this one (in yaml):

:cmock:
  :treat_as:
    P_UINT: "HEX32*"

Unity Helpers

So sometimes you make types that are truly unique, and you want to get actual details when something fails. Well, you're up for more work, but it can really pay off in large projects. First, you want to create your very own assertion macros in a file like UnityHelper.h (and optionally assertion functions in UnityHelper.c if your assertion needs are sufficiently complex -- more on that in just a moment). CMock scans the helper file header for UNITY_TEST_ASSERT_EQUAL_* macros in order to make the magic happen. These macros need to follow this pattern:

/*
 * Note: Tail end of macro name must match your type's capitalization!
 */
UNITY_TEST_ASSERT_EQUAL_custom_type(expected, actual, line, message)

Often, you will want to wrap this macro in another convenience macro that automatically inserts the line number and a NULL for message, so that you can use it directly in your tests, like so:

/*
 * Note: Capitalization is not important for this convenience macro.
 * In fact, you can name this whatever you like -- though following
 * the existing convention is probably a good idea.
 */
#define TEST_ASSERT_EQUAL_custom_type(expected, actual) 
   UNITY_TEST_ASSERT_EQUAL_custom_type(expected, actual, __LINE__, NULL)

Very likely, your original UNITY_ macro above will wrap around a C function where you will perform the comparisons using standard Unity macros (though in some cases, you may only need your custom macro to reference existing Unity macros all in your helper .h file). If you want to get really fancy, you can call TEST_FAIL_MESSAGE with whatever weird messages you may need during your checks.

/*
 * This would live in a .c file having same name as your helper .h file.
 */
void AssertEqualCustomType(custom_type expected, 
                           custom_type actual, 
                           unsigned short line)
{
  UNITY_TEST_ASSERT_EQUAL_INT(expected.x, actual.x, line, 
                              "Example Struct Failed For Field x");
  UNITY_TEST_ASSERT_EQUAL_INT(expected.y, actual.y, line, 
                              "Example Struct Failed For Field y");
}

Now all you need to do is tell CMock where your helper is. You do that by using the :unity_helper setting, like so:

:cmock:
  :unity_helper: "../unity/custom/my_unity_helper.h"

If you are using a fancy-pants build setup like Ceedling or the example project, you may also need to include "my_unity_helper.h" in your Test file, or configure it as an include in your yaml file.

Finally, if you have other custom types that really are aliases of the same thing, you can reuse your custom assert using your friend :treat_as, just like option 2 above.

:cmock:
  :treat_as:
    ANOTHER_NAME_FOR_IT: "custom_type"
    PTR_TO_CUSTOM_TYPE: "custom_type*"

How neat is that?