如何测试静态函数

时间:2009-02-27 03:33:44

标签: c unit-testing

当将单元测试应用于某些C代码时,我们遇到了一个问题,即在测试文件中无法调用某些静态函数,而无需修改源代码。有没有简单或合理的方法来解决这个问题?

12 个答案:

答案 0 :(得分:48)

我有一个测试工具。在可怕的情况下 - 比如试图测试静态函数,我使用:

#include "code_under_test.c"
...test framework...

也就是说,我在测试工具中包含了包含被测功能的整个文件。这是最后的手段 - 但它确实有效。

答案 1 :(得分:13)

您能否提供更多有关无法调用此功能的信息?

它不可用,因为它是.c文件的私有内容吗?如果是这样,最好的办法是使用允许访问函数的条件编译,以允许其他编译单元访问它。例如

SomeHeaderSomewher.h

#if UNIT_TEST
#define unit_static 
#else
#define unit_static static
#endif

foo.h中

#if UNIT_TEST
void some_method
#endif

Foo.cpp中

unit_static void some_method() ...

答案 2 :(得分:7)

对于单元测试,我们实际上在源文件本身中有测试代码,并且我们在测试时有条件地编译它。这使单元测试可以完全访问所有函数和文件级变量(静态或其他)。

单元测试本身不是静态的 - 这允许我们从单个超级测试程序调用单元测试,该程序单元测试所有编译单元。

当我们发送代码时,我们有条件地编译出单元测试,但这实际上并不是必需的(如果你想确定你正在运送完全你测试的相同代码)。< / p>

我们总是发现将单元测试与您正在测试的代码放在同一个位置是非常宝贵的,因为它更明显地表示您需要在代码更改时更新测试。

答案 3 :(得分:5)

不 - 你不能直接测试静态函数而不至少修改一下源(即C中静态的定义 - 它不能从不同文件中的函数调用)。

你可以在测试文件中创建一个单独的函数来调用静态函数吗?

例如:

//Your fn to test
static int foo(int bar)
{
  int retVal;
  //do something
  return retVal;
}

//Wrapper fn
int test_foo(int bar)
{
  return foo(bar);
}

我们通常不直接测试我们的静态函数,而是确保它们执行的逻辑通过调用函数的不同测试得到充分测试。

答案 4 :(得分:4)

静态函数本质上是公共(即公开)函数的辅助函数。所以IMO,你的单元测试应该使用输入来调用公共接口,这些输入执行静态函数中的所有路径。

应使用公共函数的输出(返回值/副作用)来测试静态效果。

这意味着你需要有适当的存根来“捕捉”这些副作用。 (例如,如果函数调用文件IO,则需要提供存根来覆盖这些文件IO lib函数)。通过使每个测试套件成为单独的项目/可执行文件并避免链接到任何外部lib函数来实现此目的的最佳方法。你甚至可以模拟C函数,但这不值得付出努力。

无论如何,这是我到目前为止使用的方法,它对我有用。祝你好运

答案 5 :(得分:3)

如果您在Unix环境下,则可以在测试文件中包含附加标头yourheader_static.h以及静态函数的声明,并将obj文件code_under_test.o翻译为objdump --globalize-symbols=syms_name_file以全局化本地符号。它们将被视为非静态函数。

答案 6 :(得分:2)

你可以添加一个非静态函数来调用静态函数,然后调用非静态函数。

static int foo ()
{
   return 3;
}

#ifdef UNIT_TEST
int test_foo ()
{
  if (foo () == 3)
    return 0;

  return 1;
}
#endif

答案 7 :(得分:2)

#define static

这是一个非常糟糕的主意。如果有一个函数声明为局部变量,它会改变函数的行为。例如:

static int func(int data)
{
   static int count = 0;

   count += data;
   return count;
}

您可以从单元测试中调用该函数,因为将导出func(),但代码的基本功能将被修改。

- 库尔特

答案 8 :(得分:1)

如果您正在使用Ceedling并尝试使用#include“code_under_test.c”方法,那么测试版本将会失败,因为它会在#included时自动尝试构建“code_under_test.c”,也因为它是目标测试。

我已经能够通过稍微修改code_under_test.c代码和其他一些更改来解决这个问题。用这个检查包装整个code_under_test.c文件:

axis

将此添加到测试工具中:

#if defined(BUILD)
...
#endif // defined(BUILD)

#define BUILD #include "code_under_test.c" define添加到Makefile或项目配置文件中:

BUILD

您的文件现在将从您的环境构建,并且包含在您的测试工具中。 Ceedling现在无法再次构建文件(确保您的project.yml文件没有定义BUILD)。

答案 9 :(得分:1)

以上建议的所有答案(少数除外)都建议条件编译,需要修改源代码。因此,这不应该是一个问题,它只会增加混乱(仅用于测试)。相反,你可以做这样的事情。

说你要测试的功能是

static int foo(int);

你创建另一个名为testing_headers.h的头文件,其中包含内容 -

static int foo(int);
int foo_wrapper(int a) {
    return foo(a);
}

现在,在编译c文件进行测试时,您可以强制在编译器选项中包含此标头。

对于clang和gcc,标志为--include。对于Microsoft C编译器,它是/FI

这将需要对您的c文件进行绝对0更改,并且您将能够为您的函数编写非静态包装器。

如果您不想要非静态包装器,您还可以创建一个初始化为foo的非静态全局函数指针。

然后,您可以使用此全局函数指针调用该函数。

答案 10 :(得分:0)

只是添加Jonathan Leffler接受的答案,并详细说明其他提到的包装函数:

  1. 创建一个测试源文件,如test_wrapper_foo.c,其中foo.c是原始文件。
  2. 在test_wrapper_foo.c中:
  3. #include "foo.c"   
    
    // Prototype
    int test_wrapper_foo();
    
    // wrapper function
    int test_wrapper_foo() {
        // static function to test
        return foo();
    }
    

    假设foo.c中的静态函数foo具有原型:int foo(void);

    1. 通过makefile而不是foo.c构建test_wrapper_foo.c(请注意,这不会破坏其他外部函数对foo.c中函数的依赖性)

    2. 在单元测试脚本中,调用test_wrapper_foo()而不是foo()。

    3. 这种方法保留了原始源的完整性,同时允许您从测试框架中访问该函数。

答案 11 :(得分:-1)

有两种方法可以做到这一点。

  1. 将c源文件包含在单元测试源文件中,因此静态方法现在属于单元测试源文件的范围并可调用。

  2. 做一招:

  3. #define static

    在单元测试源文件的头部。

    它会将关键字static转换为“无”,或者我可以说,它会从您的c源代码中删除static

    在某些单元测试工具中,我们可以使用配置选项“预处理器”来做这个技巧,只需输入配置:

    static=

    该工具会将所有static关键字转换为“无”

    但我必须说,使用这些方法会使static方法(或变量)失去其特定含义。