单元测试C代码


853

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

是否有任何项目可以像使用JUnit对Java代码进行单元测试那样简单地对普通C代码进行单元测试?非常感谢任何专门用于嵌入式开发(对arm-linux平台进行交叉编译)的见解。



2
@zmo — 软件建议是用于获取软件建议的Stack Exchange网站。我没有使用它,所以我不能说它的效果如何。您应该在发布之前检查他们的发布规则。
乔纳森·莱夫勒

Answers:


495

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

AceUnit

AceUnit(高级C和嵌入式单元)将自己视为一个舒适的C代码单元测试框架。它试图模仿JUnit 4.x,并包括类似反射的功能。AceUnit可用于资源限制环境(例如嵌入式软件开发)中,并且重要的是,它在无法包含单个标准头文件且无法从ANSI / ISO C库调用单个标准C函数的环境中也能正常运行。它还具有Windows端口。尽管作者已经表示有兴趣添加这种功能,但它不使用分叉来捕获信号。请参阅AceUnit主页

GNU Autounit

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

单位

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

单位

标准C,带有Win32 GUI实现的计划。当前不分叉或以其他方式保护单元测试的地址空间。在早期开发中。请参阅CUnit主页

铜测试

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

CppUnit

一流的C ++单元测试框架;您还可以使用它来测试C代码。它稳定,积极开发,并具有GUI界面。不使用CppUnit for C的主要原因是,它首先很大,其次,您必须使用C ++编写测试,这意味着您需要C ++编译器。如果这些听起来不令人担忧,那么与其他C ++单元测试框架一起绝对值得考虑。请参阅CppUnit主页

embUnit

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

最小单位

最小的宏集就可以了!关键是要表明对代码进行单元测试有多么容易。参见MinUnit主页

安藤先生的CUnit

这是一个相当新的CUnit实现,并且显然仍在早期开发中。有关Ando先生的主页,请参见CUnit

此列表的最新更新时间为2008年3月。

更多框架:

卡莫卡

CMocka是C的测试框架,支持模拟对象。易于使用和设置。

请参阅CMocka主页

标准

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

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

硬件

HWUT是具有C的强大支持的通用单元测试工具。它可以帮助创建Makefile,生成用最小的“迭代表”编码的大量测试用例,遍历状态机,生成C存根等等。通用方法非常独特:裁决基于“好的标准输出/不好的标准输出”。但是,比较功能很灵活。因此,任何类型的脚本都可以用于检查。它可以应用于可以产生标准输出的任何语言。

请参阅HWUT主页

绿色

适用于C和C ++的现代,可移植,跨语言单元测试和模拟框架。它提供了可选的BDD表示法,模拟库以及在单个进程中运行它的能力(使调试更加容易)。可以自动发现测试功能的测试运行程序。但是您可以以编程方式创建自己的。

所有这些功能(以及更多功能)在CGreen手册中进行了说明。

Wikipedia在“单元测试框架列表:C”下给出了C单元测试框架的详细列表。


最初,Check看起来非常牢固。我将不得不看到它在实际使用的火力下如何保持住……但是看起来确实很合适。
保罗·奥斯本

8
我们在嵌入式系统上使用check作为单元测试代码。在大多数情况下,检查是一个不错的选择,但是现在我们正在uClinux上运行的系统上工作,并且由于check需要fork,因此它不适用于那些系统。:/
David Holm

1
@labyrinth Ubuntu中的版本可以追溯到2002年。最新版本是今年(截至2014年,截止本评论)。我不得不从源代码编译它。
巴里·布朗

4
HWUT确实会生成可远程控制的存根,如果您要为与硬驱动程序交互的模块编写测试,这将非常方便。在大多数情况下,这些驱动程序在PC上不存在。HWUT文档
Frank-

1
根据Check的Github Page的说法,最新版本0.11.0201612月17日发布
Mandeep Sandhu'2

164

我个人喜欢Google Test框架

测试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 cross来完成此任务。这是有道理的,因为那里有一个完整的shell和os来收集结果。对于不太丰富的环境(我将其归类为没有完整操作系统的任何东西),您应该仅在主机上构建并运行。无论如何,您都应该这样做,以便您可以在构建过程中自动运行测试。

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

迈克尔·费瑟斯(Michael Feathers)有一本关于测试遗留代码的好书。在第一章中,他介绍了我强烈推荐的处理非OO代码的技术。

编辑:我写了一篇有关单元测试过程代码的博客文章,其源代码可以在GitHub上找到

编辑从实用程序员那里有一本新书,专门针对单元测试C代码,我强烈建议这样做


17
不要购买杂物。编书。它不包含该问题的答案中未包含的任何见解。
菲尔(Phil)

3
我知道C和C ++有很多重叠之处,但是当您生成最终将在C编译器中编译的代码时,使用C ++测试库并不是一个好主意。
拉斐尔·阿尔梅达

2
@RafaelAlmeida本质上我同意,我在这里显示的是一个预处理程序接缝,而没有将C包含包装在外部C中。无论如何,我发现C ++在实践中非常方便用作测试描述语言。我还写了一个基于C的测试框架,所以我对此并不拘泥
mikelong 2014年

@菲尔,我不同意。我发现这本书是非常有价值的,特别是人谁是不是真正的强C.
CHendrix

如上所述,我正在使用Fake Function Framework来模拟HAL函数。与gTest一起使用时效果很好。github.com/meekrosoft/fff
莱昂纳多

135

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


5
我没有从事嵌入式系统的经验,因此无法对此发表评论,但是对于小型C程序(学业,脚本),这看起来很完美。很棒的链接。
2011年

3
@toasted_flakes我已将其放入github要点:gist.github.com/sam159/0849461161e86249f849
山姆

这与我在这里开始搜索之前的想法非常接近!我想自动化测试,以便TEST(funcname,body)创建函数并存储指向该函数的指针,但是看起来我需要进行一些外部处理。
本·库什吉安

41

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

http://cutest.sourceforge.net/

它非常轻巧和简单,因此非常适合嵌入式系统。使它在目标平台以及台式机上都能正常工作没有问题。除了编写单元测试之外,还需要:

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

系统需要支持堆和某些stdio功能(并非所有嵌入式系统都具有)。但是代码很简单,如果您的平台没有这些需求,您可能可以替代这些需求。

明智地使用extern“ C” {}块,它也支持测试C ++。


1
我将对CuTest进行投票。我一直在Nintendo DS上使用它来开发自制软件,安装或使用它没有任何困难。
Theran

我会把这个第三。我在版本1.4时下载了它,并对其进行了修改以转储为XML。看来有一个版本1.5必须下载并查看。
泰勒·普莱斯

2
CuTest对我来说很好,可以测试在QNX系统上运行的代码。
杰斯·布朗宁

它声称可以像JUnit一样工作,但是我似乎很想念BeforeAfter打电话。总而言之,它很可爱。
Dragas

40

我说的和ratkok几乎一样,但是如果您对单元测试有内在的扭曲,那么...

Unity-强烈建议对C代码进行单元测试的框架。

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


5
Unity与使用CMock的自动模拟生成相结合非常好。
thegreendroid

您能为cmock建议一些好的教程吗?
melwin_jose 2015年

Ceedling精心策划了一个非常好的CMock和Unity教程:dmitryfrank.com/articles/unit_testing_embedded_c_applications
Dmitry Frank

35

您可能还想看看libtap,这是一个C测试框架,可以输出“测试任何协议”(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();
}

我为自己的项目手动滚动了自己的libtap等效文件,但是现在我知道这已经存在,因此我不再需要维护我的libtap。凉!
短暂

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

26

有一个优雅的C单元测试框架,它支持称为cmocka的模拟对象。它只需要标准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时,文档看起来很相似。这些项目相关吗?
马特·弗里德曼

6
cmocka是cmockery的继承者。我分叉了它,因为它无法维护。
2014年

21

在开始寻找一种模拟函数的方法之前,我并没有对遗留C应用程序进行测试。我非常需要模拟才能将我要测试的C文件与其他文件隔离。我尝试了cmock,我想我会采用它。

Cmock扫描头文件并根据找到的原型生成模拟函数。Mocks可以让您完美隔离地测试C文件。您要做的就是将测试文件与模拟链接而不是真实的目标文件链接。

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

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

最后一步是统一生成测试的运行程序。Cmock与统一测试框架相关。Unity与其他任何单元测试框架一样易于学习。

值得一试,很容易掌握:

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

更新1

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

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

它是一个纯C框架,支持单元测试和模拟。它与ruby无关(与Cmock相反),对外部库的依赖性很小。

因为它不会生成代码,所以需要更多的手动工作来设置模拟。因为原型不会发生太大变化,所以对于现有项目而言,这并不代表太多工作:一旦有了模拟,就无需在一段时间内进行更改(这就是我的情况)。额外的输入可以完全控制模拟。如果您不喜欢某些东西,只需更改您的模拟即可。

无需特殊的测试跑步者。您只需要创建一个测试数组并将其传递给run_tests函数。这里也需要更多的手动工作,但是我绝对喜欢一个独立的自治框架的想法。

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

总体而言,Cmockery需要对模拟有更多的了解才能上手。示例可以帮助您克服这一问题。看起来它可以用更简单的机制完成任务。


8
您应该看看cmocka.org,它是cmockery的后继者!
2013年

您能为cmock建议一些好的教程吗?
melwin_jose 2015年

LWN文章开始,然后检查cmocka的示例目录。
2015年

16

作为C的新手,我发现C中名为“ 测试驱动的开发”的幻灯片非常有用。基本上,它与标准assert()一起使用&&以传递消息,而没有任何外部依赖性。如果有人习惯了完整的堆栈测试框架,则可能不会:)


is_spare()函数中的错误使我感到非常困扰...但是感谢您的链接!我猜TDD不能捕获所有错误。
吉斯·本·肯

这是我对于C所见过的最简单的TDD方法,assert无需任何其他库或框架就可以遵循。我认为,如果您只是新手,这可能是一个起点。
kabirbaidhya

16

我们编写了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

它也有漂亮的颜色。


支持漂亮的
同事

12

CUnit

嵌入式设备是嵌入式C系统的单元测试框架。它的设计从JUnit和CUnit等复制而来,然后在某种程度上适用于Embedded C System。嵌入式单元不需要std C库。所有对象都分配给const区域。

苔丝自动化嵌入式软件的单元测试。


1
我尝试过embunit,对此感到失望。
Craig McQueen 2010年

1
例如,看到我提交的错误报告以及三年未采取行动的另一个错误报告。
Craig McQueen 2010年

12

我不使用框架,仅使用自动工具的“检查”目标支持。实现一个“主”并使用断言。

我的测试目录Makefile.am看起来像:

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

2
我们没有使用自动工具(尽管在某个时候移过来会很好)。从历史上看,我已经将main方法用于测试目的,这不是一个不好的解决方案。
Paul Osborne

11

迈克尔·费瑟(Michael Feather)的书“有效地使用旧版代码”介绍了许多C开发过程中特定于单元测试的技术。

有一些与依赖注入有关的技术,这些技术特定于C,而我在其他任何地方都没有看到过。



6

我将CxxTest用于嵌入式c / c ++环境(主要是C ++)。

我更喜欢CxxTest,因为它有一个perl / python脚本来构建测试运行器。经过一小段坡度即可进行设置(由于不必编写测试运行程序,因此尺寸较小),它非常易于使用(包括示例和有用的文档)。最多的工作是设置代码访问的“硬件”,这样我就可以有效地进行单元/模块测试。之后,很容易添加新的单元测试用例。

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

CxxTest用户指南 CxxTest Wiki


您需要的编译器可能是c ++,但是您要测试的代码仍然可以是C。CxxTest是一个非常易于使用的框架
David Sykes


5

阅读Minunit之后,我认为一个更好的方法是基于assert宏进行测试,我使用了类似于防御性编程技术的方法。因此,我使用了Minunit与标准assert混合的相同想法。您可以在k0ga的博客中看到我的框架(一个好名字可能是NoMinunit)


我现在在我的项目中使用您的utest.h。效果很好,并且很有帮助。谢谢!
约翰(Johan)






2

如果您熟悉JUnit,则建议使用CppUnit。 http://cppunit.sourceforge.net/cppunit-wiki

那是假设您有c ++编译器来进行单元测试。如果不是这样的话,我必须同意亚当·罗森菲尔德的观点,那就是要检查。


6
问题是关于C,而不是C ++
1800信息

3
否,但是C ++可以连接C库。因此,实际上,使用C ++单元测试框架测试C库可能很好。(我的公司顺便做了这件事,它比使用C单元测试框架要容易得多。)
凯文

我做同样的事情。我们有一个用C编写的实用程序库,可在C ++代码和脚本语言下使用。我们使用CppUnit进行测试,并且效果很好,因为我们可以对C和C ++使用相同的框架。
贾安

2

在对目标进行测试之前,我使用RCUNIT对PC上的嵌入式代码进行了一些单元测试。良好的硬件接口抽象很重要,否则字节序和内存映射寄存器将使您丧命。



2

API Sanity Checker — C / C ++库的测试框架:

共享的C / C ++库的基本单元测试的自动生成器。通过分析标头中的声明,它能够为参数生成合理的(在大多数情况下,但不是所有情况下)输入数据,并为API中的每个函数编写简单的(“合理”或“浅”质量)测试用例。文件。

生成的测试的质量允许检查简单用例中是否存在严重错误。该工具能够构建和执行生成的测试,并检测崩溃(段错误),中止,各种发出的信号,非零程序返回代码和程序挂起。

例子:


1

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

确保定期在交叉编译器下编译C源代码,并在可能的情况下自动进行单元测试。


1

LibU(http://koanlogic.com/libu)具有一个单元测试模块,该模块允许显式的测试套件/案例依赖性,测试隔离,并行执行和可自定义的报告格式化程序(默认格式为xml和txt)。

该库已获得BSD许可,并且包含许多其他有用的模块-网络,调试,常用数据结构,配置等-如果您在项目中需要它们,则...




0

如果您仍在寻找测试框架,则CUnitWin32是Win32 / NT平台的一种。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.