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

Tuesday
Apr192011

Mocking inline functions or macros

CMock does a lot of nice things for you. Often it works smoothly enough that you barely remember how complex its actions actually are. But, as neat as it is, it can't break the fundamental rules of C. This becomes apparent when you try to mock and inline function or a macro. Let's talk about what the issues are here and what you can do about them.

inline

Let's start by admitting that compilers vary in their support for inline functions in the first place... particularly when we start talking about embedded compilers. Your options range from no support to fine control over when a function is to be forced inline or taken as a suggestion. Most often we are somewhere in the middle. Often inline functions are treated like macros with type checking. You might use them in a single C file (a case we can ignore because it's not public and therefore not mockable anyway). You might place them in a header file. Each file that includes that header can then choose to use the inlined function in their code. For a release build, this is peachy.

But what happens when we try to mock that?

Well, first you should know that your mock header files include your original header files. This is done so that we automatically can steal... er... borrow all the same typedefs, includes, etc. But even if this wasn't done, mocking an inline function would still be a problem. Why? Any of our source files are going to pull in the real inline function. The mock functions will want to pull in the mocked version. Since the actual function is defined in the header instead of a source file, we're not swapping the mock for the normal version... we're including both. Yikes! That's not going to link properly, is it?

So by default cmock just ignores all functions tagged as "inline". You can set it up to include them all as non-inlined mocks... but it's a rare compiler that is going to be happy with that. Another solution is required.

Macros

But first let's talk about macros. The problem is very similar, actually... but even worse. If you have a macro that you want to treat like a function, it's not going to work very well. Everywhere you use that thing, it's immediately expanded. That leads to the two version problem that inline functions suffer from. Worse than that, though, is the fact that there is no separation between declaration and definition. What do we do about that?

The Solution

The solution is the same for both, actually. Let's get the bad news out of the way first: It requires you to make a small change in your code just to support testing. I know, I feel the same way. I like my release code to be pristine... if you pretend the test files don't exist, I want you to feel like my code is just amazingly solid and well designed, for no particular reason at all. But sometimes you really need to inline. And sometimes it's just way easier to test when you can mock those inlines. It's ok... you'll get over this minor inconvenience.

The good news is that even though you are going to have a small indicator that testing is occurring in your release code, it's not going to change anything in your compiled exe. Whew.

Ok, so here is the trick.

First, if you don't have it already, you want your tests to be built with TEST defined. This is a convenience define so you can tell the difference between a release and test build. The example projects tend to do this already.

Then, you are going to use this to optionally remove your inline, like so:

#ifdef TEST
void ThisGuyIsInlined(void);
#else
inline void ThisGuyIsInlined(void)
{
   //Whatever this thing does
}
#endif

And macros? The trick is very similar. During a release build, you want to have that handy macro. During a test, though, you want a function declaration of the same shape and size to put in its place (ready to be mocked).

#ifdef TEST
int ThisIsAMacro(int Arg);
#else
#define ThisIsAMacro(Arg) (Arg * 2)
#endif

So there you go. You can mock these things when you need to... and it's only a little bit ugly... Just remind yourself. You're doing something amazingly high level in a low-level language. There's bound to be a painful bump here or there, right?

Happy Mocking!