单元测试存根C辅助方法

时间:2011-02-03 02:07:10

标签: c unit-testing methods helper stub

我正在寻找一种方法来存在位于同一C文件中的辅助方法。有没有办法在不修改源文件的情况下这样做?我正在考虑使用#define将方法b替换为b_stub方法,但我认为最终会重命名方法b

以下是一个示例用例:

#include "file.h"

a(){
    b();
}

b(){
}

我正在尝试创建一个测试框架,但我希望用户只需要包含一个包含框架和存根定义的文件。

感谢。

4 个答案:

答案 0 :(得分:3)

我不确定我完全理解你的问题。

如果要调用与b不同的例程,则可以在编译时执行以下操作:

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

或者如果你总是想要调用b但希望b表现不同,你可以在编译时这样做:

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
}

或者您可以在运行时执行类似的操作(为简单起见,此处显示的是全局变量):

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

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

使用编译时示例,您可以通过更改头文件或Makefile定义来回切换。使用运行时示例,您可以使用命令行选项,环境变量,用户首选项窗格或其他任何内容来回切换。

答案 1 :(得分:1)

我找到了一个对我有用的解决方案,也许它对你有帮助。

使用MACROS本身只能让你到目前为止。如果要对某个函数执行测试,然后使用MACROS以各种不同的方式将其删除,则需要多次重建代码并单独运行每个条件。自动化很棘手 - 现在你必须有一个批处理脚本来定义不同的符号并重建代码并聚合结果。

但是,如果您使用MACROS为每个函数定义一个函数指针,那么您可以使用一个可行的解决方案,假设您可以对要测试的目标代码进行一些小修改。

以下示例受以下因素影响很大:

  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
  4. MUT代表本例中的被测模块。

    让我们假设你有四个文件:mut.h,mut.c,test_mut.h,test_mut.c。让我们假设您可以在构建时定义符号UNIT_TEST。

    mut.h将包含任何可公开访问的函数。对于这个例子,没有任何东西,所以让我们忘掉它。

    所以让我们从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
    }
    

    我们假设我们已经过单元测试吧。它工作正常。现在我们想要测试foo,但我们不想设置我们需要的所有内容以使条形图正确执行。所以我们需要存根。

    因此,请包含一个新标头test_mut.h。除了其他内容之外,您还可以在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
    

    因此,正如您所看到的,我们已经定义了一个新的函数指针,现在可以指向我们的存根/模拟或我们的实际条形函数。这个标题也将包含在test_mut.c中,因此现在可以在test_mut.c中定义存根函数 - 它们不需要在mut.c中混乱。

    现在让我们这个有用,我们需要稍微修改mut.c

    mut.c现在需要包含&#34; test_mut.h&#34;,在单元测试期间需要禁用bar()的标准声明,我们需要将函数的定义更改为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
    }
    

    您需要存根的每个函数都需要类似的#ifdef并重命名声明和定义。因此,不幸的是,您的测试代码需要稍微混乱。

    现在test_mut.c现在可以按如下方式运用您的代码:

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

    以下是我的源文件的完整列表。我在这里构建了一个轻量级测试工具,它在IAR嵌入式工作台编译器上编译并运行(我还没有尝试过其他任何东西)并生成以下输出

      

    ..   测试运行: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
    

答案 2 :(得分:0)

如果这是在框架代码而不是最终用户代码中,那么你可以做这样的事情

#ifndef UNIT_TEST_FUNC_B
b()
{
}
#endif

现在当你想在B上运行单元测试时,你定义了UNIT_TEST_FUNC_B并将存根代码包含在一个单独的模块中

或者如果您想将测试代码保存在同一个模块中,请执行此操作

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

我为定义使用了一个唯一的名称,因此您可以为不同的测试存根不同的函数。

答案 3 :(得分:0)

你必须修改源但不多。

试试这个

#define b() stub_b()

a(){
    b(); 
   }  

(b)()
{

}

现在对方法b()的调用将被stub_b()替换,而b()defination将保持不变。 :)