【问题标题】:Unit test stub C helper method单元测试存根 C 辅助方法
【发布时间】:2011-06-20 09:39:16
【问题描述】:

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

这是一个示例用例:

#include "file.h"

a(){
    b();
}

b(){
}

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

谢谢。

【问题讨论】:

  • 我很好奇。为什么不能修改源?
  • @Moron 因为我打算将它用作我正在开发的框架的一部分,因此我想避免最终用户不得不修改他们的代码
  • 如果您正在寻找一个通用的解决方案(例如,处理任何用户提供的代码,而不仅仅是一组特定的源代码文件),您可能会解析源文件以获取/生成您需要的内容.试试 GCCXML。

标签: c unit-testing methods helper stub


【解决方案1】:

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

如果你想调用与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 定义来回切换。通过运行时示例,您可以使用命令行选项、环境变量、用户首选项窗格或其他任何内容来回切换。

【讨论】:

    【解决方案2】:

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

    单独使用 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

    在本例中,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
    }
    

    假设我们已经对 bar 进行了单元测试。它工作正常。现在我们想测试 foo 但我们不想设置我们需要的所有东西以使 bar 正确执行。所以我们需要把bar去掉。

    所以让我们包含一个新的头文件 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
    

    所以,如您所见,我们已经定义了一个新的函数指针,它现在可以指向我们的存根/模拟或我们的真实 bar 函数。此标头也将包含在 test_mut.c 中,因此现在可以在 test_mut.c 中定义存根函数 - 它们不需要在 mut.c 中弄乱它。

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

    mut.c 现在需要包含“test_mut.h”,在单元测试期间需要禁用 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
    }
    

    您需要存根的每个函数都需要类似的#ifdefs 并围绕声明和定义进行重命名。因此,不幸的是,您的被测代码需要变得有点混乱。

    现在 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
    

    【讨论】:

      【解决方案3】:

      如果这是在框架代码中而不是最终用户代码中,那么您可以执行类似的操作

      #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
      

      我为定义使用了一个唯一的名称,这样您就可以为不同的测试提取不同的函数。

      【讨论】:

        【解决方案4】:

        您必须修改源代码,但不多。

        试试这个

        #define b() stub_b()
        
        a(){
            b(); 
           }  
        
        (b)()
        {
        
        }
        

        现在对方法 b() 的调用将被 stub_b() 替换,并且 b() 定义将保持不变。 :)

        【讨论】:

          猜你喜欢
          • 2010-12-24
          • 1970-01-01
          • 2013-03-06
          • 1970-01-01
          • 2014-03-24
          • 2019-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多