单元测试存根 C 辅助方法

发布于 2024-10-15 15:02:45 字数 312 浏览 7 评论 0原文

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

下面是一个示例用例:

#include "file.h"

a(){
    b();
}

b(){
}

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

谢谢。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

屋檐 2024-10-22 15:02:45

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

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

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.

撩人痒 2024-10-22 15:02:45

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

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

您需要存根的每个函数都需要类似的 #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

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
喜爱纠缠 2024-10-22 15:02:45

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

#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

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

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.

浪菊怪哟 2024-10-22 15:02:45

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

现在尝试一下

#define b() stub_b()

a(){
    b(); 
   }  

(b)()
{

}

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

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. :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文