如何在普通C中编写单元测试?

hel*_*hod 20 c unit-testing

我已经开始深入研究GLib文档并发现它还提供了一个单元测试框架.

但是你怎么能用程序语言进行单元测试呢?或者是否需要在C中编写OO?

mch*_*mch 20

单元测试只需要"切割平面"或可以进行测试的边界.测试不调用其他函数的C函数或仅调用其他函数的C函数非常简单.其中的一些示例是执行计算或逻辑运算的功能,并且本质上是功能性的.功能在同一输入总是产生相同输出的意义上.测试这些功能可以带来巨大的好处,即使它只是通常被认为是单元测试的一小部分.

更复杂的测试,例如使用模拟或存根也是可能的,但它不像在更动态的语言中那样容易,或者甚至只是像C++这样的面向对象的语言.解决这个问题的一种方法是使用#defines.这方面的一个例子就是本文单元测试OpenGL应用程序,它展示了如何模拟OpenGL调用.这允许您测试OpenGL调用的有效序列.

另一种选择是利用弱符号.例如,所有MPI API函数都是弱符号,因此如果在自己的应用程序中定义相同的符号,则实现将覆盖库中的弱实现.如果库中的符号不​​弱,则在链接时会出现重复的符号错误.然后,您可以实现有效的整个MPI C API的模拟,它允许您确保正确匹配调用,并且没有任何可能导致死锁的额外调用.也可以使用dlopen()和加载库的弱符号dlsym(),并在必要时传递调用.MPI实际上提供了强大的PMPI符号,因此没有必要dlopen()和朋友一起使用.

您可以实现C单元测试的许多好处.它稍微难点,并且可能无法获得您用Ruby或Java编写的内容所能达到的相同级别的覆盖率,但它绝对值得做.

  • 到目前为止最佳答案.您甚至展示了如何处理C中的依赖注入的实际需求,这是隔离代码到达以实现真正的单元测试的主要技术. (4认同)

Dav*_*gue 13

在最基本的层面上,单元测试只是代码的一部分,它们执行其他代码并告诉您它们是否按预期工作.

您可以使用main()函数创建一个新的控制台应用程序,该应用程序执行一系列测试功能.每个测试都会在您的应用程序中调用一个函数,并返回0表示成功或另一个值表示失败.

我会给你一些示例代码,但我真的很生气.我确信有一些框架可以让它变得更容易.

  • 如果您仍在寻找框架,请尝试此列表以获取可用的c单元测试框架:http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C (6认同)

Jak*_*ake 6

您可以使用libtap,它提供了许多功能,可以在测试失败时提供诊断功能.它的一个例子:

#include <mystuff.h>
#include <tap.h>

int main () {
    plan(3);
    ok(foo(), "foo returns 1");
    is(bar(), "bar", "bar returns the string bar");
    cmp_ok(baz(), ">", foo(), "baz returns a higher number than foo");
    done_testing;
}
Run Code Online (Sandbox Code Playgroud)

它类似于其他语言的tap库.


dbu*_*ush 5

下面是一个示例,说明如何在单个测试程序中为可能调用库函数的给定函数实现多个测试。

假设我们要测试以下模块:

#include <stdlib.h>

int my_div(int x, int y)
{
    if (y==0) exit(2);
    return x/y;
}
Run Code Online (Sandbox Code Playgroud)

然后我们创建以下测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>

// redefine assert to set a boolean flag
#ifdef assert
#undef assert
#endif
#define assert(x) (rslt = rslt && (x))

// the function to test
int my_div(int x, int y);

// main result return code used by redefined assert
static int rslt;

// variables controling stub functions
static int expected_code;
static int should_exit;
static jmp_buf jump_env;

// test suite main variables
static int done;
static int num_tests;
static int tests_passed;

//  utility function
void TestStart(char *name)
{
    num_tests++;
    rslt = 1;
    printf("-- Testing %s ... ",name);
}

//  utility function
void TestEnd()
{
    if (rslt) tests_passed++;
    printf("%s\n", rslt ? "success" : "fail");
}

// stub function
void exit(int code)
{
    if (!done)
    {
        assert(should_exit==1);
        assert(expected_code==code);
        longjmp(jump_env, 1);
    }
    else
    {
        _exit(code);
    }
}

// test case
void test_normal()
{
    int jmp_rval;
    int r;

    TestStart("test_normal");
    should_exit = 0;
    if (!(jmp_rval=setjmp(jump_env)))
    {
        r = my_div(12,3);
    }

    assert(jmp_rval==0);
    assert(r==4);
    TestEnd();
}

// test case
void test_div0()
{
    int jmp_rval;
    int r;

    TestStart("test_div0");
    should_exit = 1;
    expected_code = 2;
    if (!(jmp_rval=setjmp(jump_env)))
    {
        r = my_div(2,0);
    }

    assert(jmp_rval==1);
    TestEnd();
}

int main()
{
    num_tests = 0;
    tests_passed = 0;
    done = 0;
    test_normal();
    test_div0();
    printf("Total tests passed: %d\n", tests_passed);
    done = 1;
    return !(tests_passed == num_tests);
}
Run Code Online (Sandbox Code Playgroud)

通过重新定义assert更新布尔变量,如果断言失败,您可以继续并运行多个测试,跟踪成功的次数和失败的次数。

在每个测试开始时,将rsltassert宏使用的变量)设置为 1,并设置控制存根函数的任何变量。如果您的存根之一被多次调用,您可以设置控制变量数组,以便存根可以检查不同调用的不同条件。

由于许多库函数是弱符号,因此可以在您的测试程序中重新定义它们,以便调用它们。在调用函数进行测试之前,您可以设置多个状态变量来控制存根函数的行为并检查函数参数的条件。

如果您不能像这样重新定义,请为存根函数指定不同的名称并在代码中重新定义要测试的符号。例如,如果您想存根fopen但发现它不是弱符号,请将存根定义为my_fopen并编译文件以使用-Dfopen=my_fopen.

在这种特殊情况下,要测试的函数可能会调用exit. 这很棘手,因为exit无法返回到正在测试的函数。这是使用setjmp和有意义的罕见时期之一longjmp。您setjmp在输入函数之前使用进行测试,然后在exit您调用的存根函数中longjmp直接返回您的测试用例。

还要注意,重新定义的exit有一个特殊变量,它会检查您是否真的想退出程序并调用_exit它。如果您不这样做,您的测试程序可能无法完全退出。

此测试套件还计算尝试和失败的测试次数,如果所有测试均通过则返回 0,否则返回 1。这样,make可以检查测试失败并采取相应措施。

上面的测试代码将输出以下内容:

#include <stdlib.h>

int my_div(int x, int y)
{
    if (y==0) exit(2);
    return x/y;
}
Run Code Online (Sandbox Code Playgroud)

并且返回码将为 0。