C代码单元测试

957

我这个夏天参与开发了一款使用C语言编写的嵌入式系统。这是一项由我们公司接手的现有项目。我已经习惯了使用JUnit在Java中编写单元测试,但对于需要重构的现有代码以及添加到系统中的新代码,我不知道编写单元测试的最佳方法。

是否存在任何项目可以使像JUnit一样轻松地为C语言编写的代码进行单元测试?特别是针对嵌入式开发(交叉编译到arm-linux平台)的任何见解都将非常感激。


13
请访问 https://cmocka.org/。 - Mawg says reinstate Monica
3
@zmo — 软件推荐 是一个用于获取软件推荐的 Stack Exchange 网站。我没有使用过该网站,因此无法确定其效果如何。在发布问题之前,请先查看他们的发布规则。 - Jonathan Leffler
31个回答

561

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

AceUnit

AceUnit(高级C和嵌入式单元)是一个舒适的C代码单元测试框架。它试图模仿JUnit 4.x并包括类似反射的能力。AceUnit可以在资源受限制的环境中使用,例如嵌入式软件开发,并且在您无法包含单个标准头文件或从ANSI / ISO C库调用单个标准C函数的环境中运行良好。它还有一个Windows端口。它不使用forks来捕获信号,尽管作者已经表达了添加此功能的兴趣。请参见AceUnit主页

GNU Autounit

与Check非常相似,包括分叉以在单独的地址空间中运行单元测试(实际上,Check的原始作者从GNU Autounit借鉴了这个想法)。GNU Autounit广泛使用GLib,这意味着链接等需要特殊选项,但这可能对您来说不是一个大问题,特别是如果您已经在使用GTK或GLib。请参见GNU Autounit主页

cUnit

也使用GLib,但不会分叉以保护单元测试的地址空间。

CUnit

标准C,计划进行Win32 GUI实现。目前不会分叉或以其他方式保护单元测试的地址空间。处于早期开发阶段。请参见CUnit主页

CuTest

一个简单的框架,只有一个.c和一个.h文件,您可以将其放入源树中。请参见CuTest主页

CppUnit

用于C ++的首选单元测试框架; 您也可以使用它来测试C代码。它是稳定的,正在积极开发,并具有GUI界面。不使用CppUnit进行C的主要原因是它非常大,其次您必须使用C ++编译器编写测试。如果这些问题听起来不像关注点,那么一定值得考虑,以及其他C ++单元测试框架。请参见CppUnit主页

embUnit

embUnit(嵌入式单元)是另一个用于嵌入式系统的单元测试框架。这个似乎已经被AceUnit取代了。嵌入式单元主页

MinUnit

一个最小的宏集,仅此而已!重点是展示如何轻松地对代码进行单元测试。请参见MinUnit主页

CUnit for Mr. Ando

一个相当新的CUnit实现,显然仍处于早期开发阶段。请参见CUnit for Mr. Ando主页

此列表最后更新于2008年3月。

更多框架:

CMocka

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

请参见CMocka主页

Criterion

Criterion是一个跨平台的C语言单元测试框架,支持自动测试注册、参数化测试、理论测试,并可输出多种格式,包括TAP和JUnit XML。每个测试在其自己的进程中运行,因此可以报告或测试信号和崩溃(如果需要)。

有关更多信息,请参见Criterion主页

HWUT

HWUT是一个通用的单元测试工具,对C语言有很好的支持。它可以帮助创建Makefile,在最小的“迭代表”中生成大量的测试用例,沿着状态机走,生成C-stubs等等。总体方法非常独特:判决基于“好的stdout / 坏的stdout”。虽然比较函数是灵活的,但任何类型的脚本都可以用于检查。它可以应用于任何能够产生标准输出的语言。

请参见HWUT主页

CGreen

CGreen是一个现代的、便携的、跨语言的C和C++单元测试和模拟框架。它提供了一个可选的BDD表示法,一个模拟库,以及在单个进程中运行它(使调试更容易)。一个测试运行器可以自动发现测试函数。但是你也可以编写自己的程序。

所有这些功能(以及更多)都在CGreen手册中有解释。

维基百科列出了C语言单元测试框架列表


9
我们在嵌入式系统中使用“check”来进行单元测试。在大多数情况下,“check”是一个不错的选择,但是现在我们正在开发运行于uClinux上的系统,由于“check”需要fork,所以它在这些系统上无法工作。:/ - David Holm
1
@labyrinth 在Ubuntu中的版本是2002年的。最新版本是今年(截至本评论为止的2014年)。我不得不从源代码编译它。 - Barry Brown
4
HWUT可以生成可远程控制的存根代码,这对于编写与硬件驱动程序交互的模块测试非常方便。大多数情况下,这些驱动程序不存在于计算机上。HWUT文档 - Frank-Rene Schäfer
我在工作中使用HWUT,它运行得非常好。 - Eltges
1
根据Check的Github页面,最新版本是0.11.0,发布于2016年12月17日 - Mandeep Sandhu
显示剩余4条评论

188

个人而言,我喜欢Google测试框架

在测试C代码时真正的困难在于打破对外部模块的依赖,以便将代码隔离成单元。当您尝试在传统代码周围进行测试时,这可能会特别棘手。在这种情况下,我经常使用链接器在测试中使用存根函数。

人们谈论“缝合线”时,就是指这个问题。在C中,您唯一的选择实际上是使用预处理器或链接器来模拟您的依赖项。

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

#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来完成此操作。这是有道理的,因为您在那里有一个完整的shell和操作系统可以收集结果。对于较少丰富的环境(我将其归类为没有完整操作系统的任何内容),您应该在主机上构建和运行。无论如何,您都应该这样做,以便可以自动运行测试作为构建的一部分。
我发现测试C ++代码通常更容易,因为面向对象的代码通常比过程化的代码耦合度低得多(当然,这很大程度上取决于编码风格)。此外,在C ++中,您可以使用诸如依赖注入和方法覆盖之类的技巧,以获得封装的代码中的接缝。
Michael Feathers写了一本关于测试遗留代码的优秀书籍。在其中一章中,他介绍了处理非面向对象代码的技术,我强烈推荐。

编辑:我已经写了一篇关于单元测试过程式代码的博客文章,并且在GitHub上提供源代码

编辑:有一本新书即将出版,来自Pragmatic Programmers,专门讲解单元测试C代码,我强烈推荐


19
不要购买那本实用编程书。它并没有包含任何不在这个问题的答案中的深入见解。 - Phil
5
我知道C和C++有很多重叠之处,但我认为在编写最终将在C编译器中编译的代码时,使用C++测试库并不是一个好主意。请注意保持原意,使翻译内容通俗易懂,不要添加解释或其他信息。 - Rafael Almeida
3
基本上我同意@RafaelAlmeida的观点,我在这里展示了一个预处理器缝合线,而没有将C include包装在extern C中。不管这些,我发现C ++在实践中是一个相当方便的测试描述语言。我还编写了一个基于C的测试框架,所以我对此并不教条主义 :-) https://github.com/meekrosoft/fff - mikelong
1
@Phil 我不同意。我觉得这本书非常有价值,特别是对于那些在 C 语言方面不太强的人来说。 - CHendrix
我正在使用伪函数框架来模拟HAL函数,正如上面所述。它与gTest非常配合得很好。https://github.com/meekrosoft/fff - Leonardo

145

Minunit 是一个极其简单的单元测试框架。我正在使用它来为 AVR 微控制器代码进行单元测试。


6
我没有嵌入式系统的经验,所以对此无法发表评论,但对于小型的 C 程序(学校作业、脚本),这看起来很完美。好链接。 - AndrewKS
3
我已经将此转换成github的摘要内容: https://gist.github.com/sam159/0849461161e86249f849。 - Sam
这与我在开始搜索之前想出的非常接近!我想自动化测试,以便TEST(funcname,body)创建函数并存储指向该函数的指针,但看起来我需要进行一些外部处理。 - Ben Kushigian

50

我说的和ratkok几乎一样,但如果你在单元测试中嵌入了某些变化,那么...

Unity - 高度推荐用于C语言单元测试的框架。

#include <unity.h>

void test_true_should_be_true(void)
{
    TEST_ASSERT_TRUE(true);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_true_should_be_true);
    return UNITY_END();
}

在这个帖子中提到的书《嵌入式C语言测试驱动开发》中的示例是使用Unity(和CppUTest)编写的。


7
使用CMock自动化生成模拟对象结合Unity使用效果不错。 - thegreendroid
1
你能推荐一些关于cmock的好教程吗? - melwin_jose
有一个非常好的由Ceedling组织的CMock和Unity教程:http://dmitryfrank.com/articles/unit_testing_embedded_c_applications - Dmitry Frank

43

我目前正在使用CuTest单元测试框架:

http://cutest.sourceforge.net/

它非常轻量级和简单,非常适合嵌入式系统。我在目标平台和桌面上使用它没有遇到任何问题。除了编写单元测试之外,只需要:

  • 一个头文件包含在调用CuTest例程的任何位置
  • 一个单独的'C'文件被编译/链接到映像中
  • 添加一些简单的代码到主函数中设置并调用单元测试 - 我只是将其放在一个特殊的main()函数中,如果在构建过程中定义了UNITTEST,则进行编译。

该系统需要支持堆和一些stdio功能(并非所有嵌入式系统都具备这些功能)。但是代码足够简单,您可以在不具备这些要求的情况下尝试替代方法。

通过谨慎使用extern "C" {}块,它也支持对C ++的测试。


1
我也推荐使用CuTest。我一直在为Nintendo DS开发自制软件时使用它,设置和使用都非常容易。 - Theran
我也同意这个观点。我在1.4版本时下载了它并修改为转储到XML。看起来有一个1.5版本,我需要下载并查看一下。 - Taylor Price
2
CuTest对我来说非常适合测试在QNX系统上运行的代码。 - Jace Browning
它声称像JUnit一样工作,但我似乎错过了BeforeAfter的调用。总的来说,它很可爱。 - Dragas
CuTest非常好用!它已经提供了make-tests.sh脚本,您不必编写main()并添加所有测试...只需添加要测试的函数即可开始!我们将其用于嵌入式系统,并已经发现了一些遗留问题。与fff.h https://github.com/meekrosoft/fff 一起使用,可以轻松模拟函数等。 - Joel Ravazzolo

37

您也许想查看 libtap,这是一个 C 语言测试框架,可以输出“测试任何协议”(Test Anything Protocol,TAP),因此可以与为这项技术开发的各种工具很好地集成。它主要用于动态语言世界,但易于使用并越来越受欢迎。

例如:

#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();
}

1
ok(TESTING==IsSimple(), "libtap is super easy to use") - AShelly

28

有一个名为cmocka的优雅C语言单元测试框架,它支持模拟对象。它只需要标准的C库,在许多计算平台(包括嵌入式)和不同编译器上都可以工作。

它还支持不同的消息输出格式,如Subunit、Test Anything Protocol和jUnit XML报告。

cmocka已经被创建用于在嵌入式平台上工作,并且也支持Windows。

一个简单的测试看起来像这样:

#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月发布。


3
当我查看cmockery和cmocka的文档时,它们看起来相似。这些项目是否相关? - Matt Friedman
7
cmocka 是 cmockery 的继承者。我已经 fork 了它,因为它没有维护。 - asn

21

在测试一个传统的C应用程序时,我并没有取得很大的进展,因此开始寻找一种模拟函数的方法。我非常需要使用模拟函数来使我要测试的C文件与其他文件隔离。我尝试了cmock,并且认为我会采用它。

Cmock扫描头文件并根据其找到的原型生成模拟函数。模拟函数将允许您完全隔离地测试C文件。您所需做的就是将测试文件与模拟函数链接,而不是与真实的对象文件链接。

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

测试由典型的testA()和testB()函数组成,在其中构建期望值,调用函数进行测试并检查断言。

最后一步是使用Unity为您的测试生成运行器。 Cmock与Unity测试框架紧密相连。 Unity像任何其他单元测试框架一样容易学习。

非常值得一试,而且相当容易掌握:

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

更新1

我正在调查的另一个框架是Cmockery。

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

它是一个纯C框架,支持单元测试和模拟。它没有依赖于Ruby(与Cmock相反),并且对外部库的依赖非常少。

虽然它不会生成代码,但需要一些手动工作来设置模拟函数。这对于现有项目来说并不需要太多的工作,因为原型不会经常更改:一旦您拥有了模拟函数,您就不需要长时间更改它们(这是我的情况)。额外的输入提供完全控制模拟函数。如果您不喜欢某些东西,只需更改模拟函数即可。

不需要特殊的测试运行程序。你只需要创建一个测试数组并将其传递给run_tests函数即可。这里需要做更多的手动工作,但我绝对喜欢这种自包含自治框架的想法。
此外,它还包含了一些我不知道的巧妙的C技巧。
总的来说,要开始使用Cmockery,需要对mock有更多的理解。例如可以帮助你克服这个问题。看起来它可以使用更简单的机制来完成工作。

8
你应该看一下http://cmocka.org/,它是cmockery的继任者! - asn
你能推荐一些关于cmock的好教程吗? - melwin_jose
LWN文章开始,然后检查cmocka的示例目录。 - asn

19

我们编写了CHEAT(托管在GitHub上),以便易于使用和可移植性。

它没有任何依赖项,不需要安装或配置。只需要一个头文件和一个测试案例。

#include <cheat.h>

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

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

$ gcc -I . tests.c
$ ./a.out
..
---
2 successful of 2 run
SUCCESS

它的颜色也很漂亮。


@Tuplanolla 看起来链接http://users.jyu.fi/~sapekiis/cheat/已经失效了。但是无论如何,github为我提供了足够的信息来开始使用CHEAT。非常感谢您的努力:) - dehasi

16
作为一个C语言新手,我发现名为在C中进行测试驱动开发的幻灯片非常有帮助。基本上,它使用标准的assert()&&来传递消息,没有任何外部依赖。如果有人习惯了完整的堆栈测试框架,那么这可能不太适合 :)

这是我见过的最简单的C语言TDD方法,你可以只用assert而无需任何其他库或框架就能实现。我认为如果你是一名新手,这可能是一个很好的起点。 - kabirbaidhya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接