单元测试C代码

单元测试C代码

Unit Testing C Code

今年夏天,我在一个用C语言编写的嵌入式系统上工作。我为之工作的公司接管了一个现有的项目。我已经习惯于使用JUnit在Java中编写单元测试,但对于编写现有代码(需要重构)的单元测试以及添加到系统中的新代码的最佳方式,感到茫然。

有没有办法让单元测试简单C代码和单元测试Java代码一样容易,例如JUnit?任何特别适用于嵌入式开发(交叉编译到ARM Linux平台)的见解都将受到极大的赞赏。


检查C中的一个单元测试框架;这里可以找到C中的一个单元测试框架列表,并在下面复制。根据运行时有多少个标准库函数,您是否可以使用其中一个函数。

AceUnit

AceUnit (Advanced C and Embedded Unit) bills itself as a comfortable C code unit test framework. It tries to mimick JUnit 4.x and includes reflection-like capabilities. AceUnit can be used in resource constraint environments, e.g. embedded software development, and importantly it runs fine in environments where you cannot include a single standard header file and cannot invoke a single standard C function from the ANSI / ISO C libraries. It also has a Windows port. It does not use forks to trap signals, although the authors have expressed interest in adding such a feature. See the AceUnit homepage.

GNU Autounit

Much along the same lines as Check, including forking to run unit tests in a separate address space (in fact, the original author of Check borrowed the idea from GNU Autounit). GNU Autounit uses GLib extensively, which means that linking and such need special options, but this may not be a big problem to you, especially if you are already using GTK or GLib. See the GNU Autounit homepage.

cUnit

Also uses GLib, but does not fork to protect the address space of unit tests.

CUnit

Standard C, with plans for a Win32 GUI implementation. Does not currently fork or otherwise protect the address space of unit tests. In early development. See the CUnit homepage.

CuTest

A simple framework with just one .c and one .h file that you drop into your source tree. See the CuTest homepage.

CppUnit

The premier unit testing framework for C++; you can also use it to test C code. It is stable, actively developed, and has a GUI interface. The primary reasons not to use CppUnit for C are first that it is quite big, and second you have to write your tests in C++, which means you need a C++ compiler. If these don’t sound like concerns, it is definitely worth considering, along with other C++ unit testing frameworks. See the CppUnit homepage.

embUnit

embUnit (Embedded Unit) is another unit test framework for embedded systems. This one appears to be superseded by AceUnit. Embedded Unit homepage.

MinUnit

A minimal set of macros and that’s it! The point is to show how easy it is to unit test your code. See the MinUnit homepage.

CUnit for Mr. Ando

A CUnit implementation that is fairly new, and apparently still in early development. See the CUnit for Mr. Ando homepage.

This list was last updated in March 2008.

更多框架:卡莫卡

CMocka是一个支持模拟对象的C测试框架。它很容易使用和设置。

请参见cmocka主页。

标准

标准是一个支持自动测试注册、参数化测试、理论的跨平台C单元测试框架,可以输出多种格式,包括tap和junit xml。每个测试都是在自己的过程中运行的,因此如果需要,可以报告或测试信号和崩溃。

更多信息请参见标准主页。

HWUT

hwut是一个通用的单元测试工具,它对C有很大的支持。它可以帮助创建makefile,生成大量用最小的"迭代表"编码的测试用例,遍历状态机,生成C-stub等等。一般的方法是非常独特的:判决基于"好的stdout/坏的stdout"。不过,比较函数是灵活的。因此,可以使用任何类型的脚本进行检查。它可以应用于任何能产生标准输出的语言。

请参见hwut主页。

C格林

一个现代的、可移植的、跨语言的单元测试和模拟框架,用于C和C++。它提供了可选的BDD符号、模拟库,能够在单个进程中运行它(使调试更容易)。可以使用自动发现测试功能的测试运行程序。但是您可以通过编程创建自己的。

cgreen手册中解释了所有这些特性(以及更多)。

维基百科在单元测试框架列表下给出了C单元测试框架的详细列表:C


我个人喜欢谷歌测试框架。

测试C代码的真正困难是打破对外部模块的依赖性,这样您就可以用单元来隔离代码。当您试图围绕遗留代码进行测试时,这可能特别有问题。在这种情况下,我经常发现自己使用链接器在测试中使用存根函数。

这就是人们谈论"接缝"时所指的。在C语言中,您唯一的选择实际上是使用预处理器或链接器来模拟您的依赖关系。

我的一个C项目中的一个典型测试套件可能如下所示:

1
2
3
4
5
6
7
8
9
#include"myimplementationfile.c"
#include <gtest/gtest.h>

// Mock out external dependency on mylogger.o
void Logger_log(...){}

TEST(FactorialTest, Zero) {
    EXPECT_EQ(1, Factorial(0));
}

请注意,实际上包含的是C文件,而不是头文件。这使得可以访问所有静态数据成员。在这里,我模拟了我的记录器(可能在logger.o中),并给出了一个空的实现。这意味着测试文件独立于代码库的其余部分进行编译和链接,并独立执行。

至于交叉编译代码,要使其正常工作,您需要在目标上使用良好的工具。我在PowerPC体系结构上对编译到Linux的GoogleTest进行了测试。这是有道理的,因为你有一个完整的外壳和操作系统来收集你的结果。对于不太丰富的环境(我将其归类为没有完整操作系统的任何环境),您应该只在主机上构建和运行。无论如何,您应该这样做,这样您就可以作为构建的一部分自动运行测试。

我发现测试C++代码通常容易得多,这是因为OO代码一般比过程更少耦合(当然这很大程度上取决于编码风格)。同样在C++中,您可以使用依赖注入和方法重写等技巧来将接缝嵌入到其他封装的代码中。

Michael Feathers有一本关于测试遗留代码的好书。在一章中,他介绍了处理非OO代码的技术,我强烈推荐。

编辑:我写了一篇关于单元测试过程代码的博客文章,文章的源代码在GitHub上可用。

编辑:有一本来自语用程序员的新书,专门介绍单元测试C代码,我强烈推荐。


MinUnit是一个非常简单的单元测试框架。我用它来对AVR的C微控制器代码进行单元测试。


我目前正在使用最精简的单元测试框架:

http://cutest.sourceforge.net网站/

它是嵌入式系统的理想选择,因为它非常轻和简单。让它在目标平台和桌面上工作没有问题。除了编写单元测试之外,所需要的就是:

  • 包含在任何位置的头文件你叫最可爱的套路
  • 一个附加的"c"文件编译/链接到图像中
  • 一些简单的代码添加到主目录设置并调用单元测试-i把这个放在一个特别的主目录中()。函数,如果UnitTest是在建造。

系统需要支持一个堆和一些stdio功能(并非所有嵌入式系统都有)。但是代码非常简单,如果您的平台没有这些需求,您可能可以在这些需求的替代方案中工作。

通过使用外部的"C"{}块,它也支持测试C++。


我说的几乎和Ratkok一样,但是如果你对单元测试有一个嵌入的扭曲…

Unity-强烈推荐用于单元测试C代码的框架。

本书中提到的嵌入式C的TDD线程示例是使用Unity(和CPputest)编写的。


您可能还想看看libtap,这是一个C测试框架,它输出测试任何东西协议(test anything protocol,TAP),因此可以很好地与该技术的各种工具集成。它主要用于动态语言世界中,但使用起来很容易,并且非常流行。

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <tap.h>

int main () {
    plan(5);

    ok(3 == 3);
    is("fnord","eek","two different strings not that way?");
    ok(3 <= 8732,"%d <= %d", 3, 8732);
    like("fnord","f(yes|no)r*[a-f]$");
    cmp_ok(3,">=", 10);

    done_testing();
}

有一个优雅的C单元测试框架,支持名为cmocka的模拟对象。它只需要标准的C库,在一系列计算平台(包括嵌入式)上工作,并使用不同的编译器。

它还支持不同的消息输出格式,如子单元、测试任何协议和JUnitXML报告。

CMocka也被创建为在嵌入式平台上工作,并且还支持Windows。

一个简单的测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

/* A test case that does nothing and succeeds. */
static void null_test_success(void **state) {
    (void) state; /* unused */
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(null_test_success),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}

API是完全文档化的,有几个示例是源代码的一部分。

要开始使用cmocka,您应该阅读有关lwn.net的文章:使用c中的模拟对象进行单元测试。

CMocka 1.0已于2015年2月发布。


在我开始寻找模仿函数的方法之前,我对一个遗留的C应用程序进行了测试。我非常需要mocks来将我要测试的C文件与其他文件隔离开来。我试了一下,我想我会接受的。

cmock扫描头文件并根据找到的原型生成模拟函数。mocks将允许您在完全隔离的情况下测试C文件。您所要做的就是将测试文件链接到mock,而不是真正的对象文件。

cmock的另一个优点是,它将验证传递给模拟函数的参数,并允许您指定模拟应该提供什么返回值。这对于测试函数中的不同执行流非常有用。

测试由典型的tesa()和testb()函数组成,在这些函数中构建期望、调用函数来测试和检查断言。

最后一步是用Unity为您的测试生成一个运行程序。CMock绑定到统一测试框架。Unity和任何其他单元测试框架一样容易学习。

很值得一试,而且很容易掌握:

http://sourceforge.net/apps/trac/cmock/wiki

更新1

我正在研究的另一个框架是cMockry。

http://code.google.com/p/cmockery网站/

它是一个纯C框架,支持单元测试和模拟。它不依赖Ruby(与cmock相反),并且对外部lib的依赖性非常小。

它需要更多的手工工作来设置模拟,因为它不生成代码。对于一个现有的项目来说,这并不代表很多工作,因为原型不会改变太多:一旦你有了你的模型,你就暂时不需要改变它们了(这是我的情况)。额外的输入提供了对模拟的完全控制。如果有什么东西你不喜欢,你只需改变你的模拟。

不需要特殊的测试运行程序。您只需要创建一个测试数组并将其传递给运行测试函数。这里也有一些手工操作,但我绝对喜欢一个独立的自治框架的想法。

另外,它还包含一些我不知道的漂亮的C技巧。

总的来说,cMockry需要对mock有一点了解才能开始。举例可以帮助你克服这一点。看起来它能用更简单的机械来完成这项工作。


作为一个C新手,我发现C中称为测试驱动开发的幻灯片非常有用。基本上,它使用标准的assert()&&来传递消息,而不需要任何外部依赖。如果有人习惯于一个完整的堆栈测试框架,这可能不会发生:)


有CUnit

嵌入式单元是嵌入式C系统的单元测试框架。它的设计是从junit和cunit等多个版本中复制出来的,然后在一定程度上适应了嵌入式C系统。嵌入式单元不需要标准C库。所有对象都分配给常量区域。

泰西自动化了嵌入式软件的单元测试。


为了方便使用和可移植性,我们编写了欺骗(托管在GitHub上)。

它没有依赖关系,不需要安装或配置。只需要一个头文件和一个测试用例。

1
2
3
4
5
6
#include <cheat.h>

CHEAT_TEST(mathematics_still_work,
    cheat_assert(2 + 2 == 4);
    cheat_assert_not(2 + 2 == 5);
)

测试编译成一个可执行文件,负责运行测试并报告其结果。

1
2
3
4
5
6
$ gcc -I . tests.c
$ ./a.out
..
---
2 successful of 2 run
SUCCESS

它的颜色也很漂亮。


MichaelFeather的书"有效地使用遗留代码"在C开发期间提供了许多特定于单元测试的技术。

有一些与依赖注入相关的技术是特定于C的,我在其他地方没有见过。


我不使用框架,只使用自动工具"检查"目标支持。实现"main"并使用断言。

我的test dir makefile.am如下所示:

1
2
3
4
5
6
7
check_PROGRAMS = test_oe_amqp

test_oe_amqp_SOURCES = test_oe_amqp.c
test_oe_amqp_LDADD = -L$(top_builddir)/components/common -loecommon
test_oe_amqp_CFLAGS = -I$(top_srcdir)/components/common -static

TESTS = test_oe_amqp

cpputest-单元测试C代码的强烈推荐框架。

本书中提到的嵌入式C的TDD线程示例是使用cpputest编写的。


我使用CXXTEST作为嵌入式C/C++环境(主要是C++)。

我更喜欢cxtest,因为它有一个perl/python脚本来构建测试运行程序。经过一个小的斜坡来设置它(由于不需要编写测试运行程序,所以仍然要小一些),它非常容易使用(包括示例和有用的文档)。最主要的工作是设置代码访问的"硬件",这样我就可以有效地进行单元/模块测试。之后,很容易添加新的单元测试用例。

如前所述,它是C/C++单元测试框架。所以你需要一个C++编译器。

《cxtest用户指南》CXXT维基


除了我明显的偏见

http://code.google.com/p/seatest网站/

是一个很好的简单方法来单元测试C代码。模拟x单元


在阅读了细节之后,我认为更好的方法是将测试建立在断言宏的基础上,我使用了很多类似于防御程序技术的方法。所以我使用了同样的minunit和标准断言的概念。你可以在K0GA的博客中看到我的框架(一个好名字可能是nominUnit)


cmockery网址:http://code.google.com/p/cmockery/


Google有很好的测试框架。https://github.com/google/googletest/blob/master/googletest/docs/primer.md网站

是的,就我所见,它将与普通C一起工作,即不需要C++特性(可能需要C++编译器,不确定)。


CMockery是最近启动的一个项目,它包含一个非常简单易用的C库,用于编写单元测试。


首先,看看这里:http://en.wikipedia.org/wiki/list_-unity_-testing_-frameworks_c

我公司有客户使用的C库。我们使用CXXTEST(一个C++单元测试库)来测试代码。CPP也会工作。如果你被困在C区,我建议你使用RCUnit(但是CUnit也很好)。


如果你熟悉Junit,那么我推荐CPP。http://cppunit.sourceforge.net/cppunit-wiki

假设你有C++编译器来做单元测试。如果不是的话,我必须同意亚当·罗森菲尔德的观点,那支票就是你想要的。


在对目标进行测试之前,我使用RCUnit对PC上的嵌入式代码进行了一些单元测试。良好的硬件接口抽象是很重要的,否则,端节点和内存映射寄存器会杀死您。


试试LCUT!-网址:http://code.google.com/p/lcut


API安全性检查器:C/C++库的测试框架:

An automatic generator of basic unit tests for a shared C/C++ library. It is able to generate reasonable (in most, but unfortunately not all, cases) input data for parameters and compose simple ("sanity" or"shallow"-quality) test cases for every function in the API through the analysis of declarations in header files.

The quality of generated tests allows to check absence of critical errors in simple use cases. The tool is able to build and execute generated tests and detect crashes (segfaults), aborts, all kinds of emitted signals, non-zero program return code and program hanging.

实例:

  • fontconfig 2.8.0的测试套件
  • FreeType 2.4.8的测试套件

libu(http://koanlogic.com/libu)有一个单元测试模块,允许显式的测试套件/用例依赖性、测试隔离、并行执行和可定制的报告格式化程序(默认格式为XML和TXT)。

该库是BSD许可的,包含许多其他有用的模块-网络、调试、常用的数据结构、配置等-如果您的项目中需要它们…


一种使用的技术是用C++ XUngt框架(和C++编译器)开发单元测试代码,同时将目标系统的源保持为C模块。

确保您定期在交叉编译器下编译您的C源代码,如果可能的话,使用单元测试自动编译。


我很惊讶没有人提到Cutter(http://cutter.sourceforge.net/)您可以测试C和C++,它与AutoTo工具无缝集成,并有一个非常好的教程可用。


如果您的目标是win32平台或NT内核模式,那么您应该看看cfix。


我只是出于对现有C单元测试库的失望而写了libcut。它具有基本体的自动类型字符串(不需要测试eq_int、测试eq_long、测试eq_short等;只有两个不同的基本体和字符串集),由一个头文件组成。下面是一个简短的例子:

1
2
3
4
5
6
7
8
9
10
#include <libcut.h>

LIBCUT_TEST(test_abc) {
    LIBCUT_TEST_EQ(1, 1);
    LIBCUT_TEST_NE(1, 0);
    LIBCUT_TEST_STREQ("abc","abc");
    LIBCUT_TEST_STRNE("abc","def");
}

LIBCUT_MAIN(test_abc);

不过,它只适用于C11。


如果您仍在寻找测试框架,那么cunitwin32是用于win32/nt平台的。

这解决了我在其他测试框架中面临的一个基本问题。也就是说,全局/静态变量处于确定性状态,因为每个测试都作为一个单独的过程执行。


推荐阅读