ORBstacle
ORBstacle

Reputation: 21

Unit test stub C helper method

I am looking for a way to stub out a helper method located within the same C file. Is there any way of doing this without modifying the source file? I was thinking something along the lines of using a #define to replace method b with a b_stub method, but I think this will end up renaming the method b

Here's a sample use case:

#include "file.h"

a(){
    b();
}

b(){
}

I am attempting to create a testing framework, but I want the user to only have to include a single file containing the framework and stub definitions.

Thanks.

Upvotes: 2

Views: 4092

Answers (4)

Nick
Nick

Reputation: 1413

I found a solution to this that works for me, maybe it will help you too.

Using MACROS by themselves can only get you so far. If you want to perform a test on a function but then stub it out in various different ways using MACROS will require you to rebuild your code several times and run each condition individually. This is tricky to automate - now you have to have a batch script that will define different symbols and rebuild the code and aggregate the results.

However, if you use MACROS to define a function pointer for each function you which to stub out you have a workable solution assuming you can make some minor modifications to the target code you wish to test.

The following example was heavily influenced by the following:

  1. http://eradman.com/posts/tdd-in-c.html
  2. http://locklessinc.com/articles/mocking/
  3. http://www.embedded.com/design/programming-languages-and-tools/4007177/2/Doing-C-code-unit-testing-on-a-shoestring-Part-1-The-basics-and-the-tools

MUT stands for module under test in this example.

Lets assume you have four files: mut.h, mut.c, test_mut.h ,test_mut.c. Lets also assume you can define a symbol UNIT_TEST when you build this.

mut.h would include any functions that are publicly accessible. For this example there aren't any, so lets forget it.

So lets start off with a version of mut.c

#include <cstdbool>
#include "mut.h"

static bool foo(int baz);
static bool bar(int baz);

static bool foo(int baz)
{
    bool ret = false;

    if(bar(baz))
    {
        //do something
        ret = true;
    }
    else
    {
        ret = false;
    }
    return ret;
}

static bool bar(int baz)
{
    //Black box mystery / Don't care
}

Let's assume we've already unit tested bar. It works fine. Now we want to test foo but we don't feel like setting everything up that we need to in order to make bar execute properly. So we need to stub bar out.

So lets include a new header, test_mut.h. Among other things you would have the following in test_mut.h

#ifdef UNIT_TEST
...

//Convert every static definition into an extern declaration.
#define static extern

bool bar_mock_true (int baz);
bool bar_mock_false(int baz);
bool bar_real      (int baz);

extern bool(*bar_ptr)(int baz);
#define bar bar_ptr

...
#endif

So, as you can see we've define a new function pointer that can now point to our stubs/mocks or to our real bar function. This header will also be included in test_mut.c so the stubbed functions can now be defined inside of test_mut.c - they don't need to be in mut.c cluttering it up.

Now lets make this useful, we need to modify mut.c slightly

mut.c will now need to include "test_mut.h", the standard declaration of bar() needs to be disabled during unit testing, and we need to change the definition of the function to bar_real()

...
#include "test_mut.h"
...
#ifdef UNIT_TEST
static bool bar(int baz);
#endif

...

#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
    //Black box mystery / Don't care
}

Every function you need to stub out will need similar #ifdefs and renaming around the declaration and definition. So your code under test will need to get a little bit cluttered unfortunately.

Now test_mut.c can now exercise your code as follows:

#include <cstdbool>
#include "test_mut.h"

...

UT_STATIC void test_foo(void)
{
    int baz = 0;
    extern bool foo(int baz);
    //Test Case A
    bar_ptr = bar_mock_true;
    TEST_ASSERT(foo(baz), "Condition A");

    //Test Case B
    bar_ptr = bar_mock_false;
    TEST_ASSERT(!foo(baz), "Condition B");
}

bool bar_mock_true(int baz)
{
    return true;
}

bool bar_mock_false(int baz)
{
    return false;
}

Here are some full listings of my source files. I've built a lightweight test harness here and it compiles and runs on the IAR embedded workbench compiler (I haven't tried it on anything else) and produces the following output

.. Tests Run: 2

mut.c

#include <cstdbool>

#include "mut.h"
#include "test_mut.h"

static bool foo(int baz);

#ifndef UNIT_TEST
static bool bar(int baz);
#endif

static bool foo(int baz)
{
    bool ret = false;

    if(bar(baz))
    {
        //do something
        ret = true;
    }
    else
    {
        ret = false;
    }
    return ret;
}

#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
    //Black box mystery / Don't care
}

test_mut.h

#ifdef UNIT_TEST
#ifndef _TEST_MUT_H
#define _TEST_MUT_H

//Handle to your test runner
void test_mut(void);

//Track the number of test cases that have executed
extern int tests_run;

//Convert every static definitions into extern declarations.
#define static extern

//An alternate definition of static for the test barness to use
#define UT_STATIC static

bool bar_mock_true   (int baz);
bool bar_mock_false  (int baz);
bool bar_real        (int baz);

extern bool(*bar_ptr)(int baz);

#define bar bar_ptr

//Test Macros
#define TEST_FAIL(name)                                                           \
do                                                                                \
{                                                                                 \
    printf("\nTest \"%s\" failed in %s() line %d\n", (name), __func__, __LINE__); \
} while(0)

#define TEST_ASSERT(test_true,test_name)                                          \
do                                                                                \
{                                                                                 \
    tests_run++;                                                                  \
    if(!(test_true))                                                              \
    {                                                                             \
        TEST_FAIL(test_name);                                                     \
    }                                                                             \
    else                                                                          \
    {                                                                             \
        printf(".");                                                              \
    }                                                                             \
} while(0)

//... Insert any other macro instrumentation you may need...

#endif // _TEST_MUT_H
#endif // UNIT_TEST

test_mut.c

#ifdef UNIT_TEST

#include <cstdbool>
#include <cstdio>
#include "test_mut.h"
#include "mut.h"

UT_STATIC void test_foo(void);

int tests_run = 0;

inline UT_STATIC void test_report(void);

void test_mut(void) {
    //call your setup function(s)
    test_foo();
    //call your teardown function(s)

    test_report();
}

inline UT_STATIC void test_report(void)
{
    printf("\nTests Run: %d\n", tests_run);
}

void main(void)
{
    test_mut();
}

//Setup the function pointer for bar, by default it will point to the real
//bar function, and not a stub.
bool(*bar_ptr)(int baz) = bar_real;

UT_STATIC void test_foo(void)
{
    int baz = 0;
    extern bool foo(int baz);

    //Test Case A
    bar_ptr = bar_mock_true;
    TEST_ASSERT(foo(baz), "Condition A");

    //Test Case B
    bar_ptr = bar_mock_false;
    TEST_ASSERT(!foo(baz), "Condition B");
}

bool bar_mock_true(int baz)
{
    return true;
}

bool bar_mock_false(int baz)
{
    return false;
}

#endif

Upvotes: 1

Phoenix
Phoenix

Reputation: 1

You have to modify the source but not much.

Try this

#define b() stub_b()

a(){
    b(); 
   }  

(b)()
{

}

now calls to method b() will be replaced by stub_b() and b() defination will remain unchanged. :)

Upvotes: 0

sbass
sbass

Reputation: 489

If this is in the framework code and not end user code then you can do something like this

#ifndef UNIT_TEST_FUNC_B
b()
{
}
#endif

and now when you want to run the unit test on B you define UNIT_TEST_FUNC_B and include the stub code in a separate module

or if you want to keep the test code in the same module, you do this

#ifndef UNIT_TEST_FUNC_B
b()
{
}
#else
b()
{
// test code goes here
}
#endif

I used a unique name for the define so you can stub out different functions for different tests.

Upvotes: 0

LavaSlider
LavaSlider

Reputation: 2504

I am not sure I completely understand your question.

If you want to call a different routine than b then you can do it at compile time as:

a() {
#ifdef USE_STUB
    b_stub();
#else
    b();
#endif
}

Or if you always want to call b but want b to behave differently, you can do it at compile time as:

a() {
    b():
}
b() {
#ifdef USE_STUB
    printf( "I am in the stub version of b()\n" );
#else
    printf( "I am in the real version of b()\n" );
#endif
}

Or you can do similar things at runtime with (shown here with a global variable for simplicity):

a() {
    extern int _use_stub;
    if( _use_stub ) {
        b_stub();
    } else {
        b();
    }
}

or

a() {
    b();
}
b() {
    extern int _use_stub;
    if( _use_stub ) {
        printf( "This is the stub code\n" );
    } else {
        printf( "This is the real code\n" );
    }
}

With the compile-time examples you can switch back and forth by changing a header file or a Makefile definition. With the runtime examples you can switch back and forth with a command line option, environment variable, user preference pane, or anything else.

Upvotes: 3

Related Questions