提问



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


有没有办法让单元测试普通C代码就像使用 JUnit 单元测试Java代码一样简单?任何专门针对嵌入式开发(交叉编译到arm-linux平台)的见解都将非常感激。

最佳参考


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



??

AceUnit


??
??AceUnit(高级C和嵌入式单元)将自己称为舒适的C代码单元测试框架。它试图模仿JUnit 4.x并包含类似反射的功能。 AceUnit可用于资源约束环境,例如嵌入式软件开发,重要的是它在不能包含单个标准头文件且无法从ANSI/ISO C库调用单个标准C函数的环境中运行良好。它还有一个Windows端口。虽然作者表示有兴趣添加这样的功能,但它并没有使用分叉来捕获信号。请参见AceUnit主页。[11]

??
??

GNU Autounit


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

??
??

CUNIT


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

??
??

CUNIT


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

??
??

可爱


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

??
??

的CppUnit


??
??C ++的首要单元测试框架;你也可以用它来测试C代码。它稳定,积极开发,并具有GUI界面。不使用CppUnit for C的主要原因首先是它非常大,其次你必须用C ++编写测试,这意味着你需要一个C ++编译器。如果这些听起来不像是关注点,那么它与其他C ++单元测试框架一起绝对值得考虑。请参阅CppUnit主页。[15]

??
??

embUnit


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

??
??

MinUnit


??
??一组最小的宏,就是这样!关键是要展示对代码进行单元测试是多么容易。请参见MinUnit主页。[17]

??
??

安藤先生的CUnit


??
??一个相当新的CUnit实现,显然仍处于早期开发阶段。请参阅安藤先生主页的CUnit。[18]

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



其他:


CMocka



CMocka是C的测试框架,支持模拟对象。它易于使用和设置.CMocka官方主页。[19]


标准



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


HWUT



HWUT是一个通用的单元测试工具,对C有很好的支持。它可以帮助创建Makefile,生成在最小迭代表中编码的大量测试用例,沿着状态机走,生成C-stub等等。一般方法非常独特:Verdicts基于良好的stdout/bad stdout。但是,比较功能是灵活的。因此,可以使用任何类型的脚本进行检查。它可以应用于任何可以产生标准输出的语言。见HWUT主页。[21]


维基百科在单元测试框架列表下提供了C单元测试框架的详细列表:C [22]

其它参考1


我个人喜欢Google Test框架。 [23]


测试C代码的真正困难在于打破了对外部模块的依赖性,因此您可以单独隔离代码。当您尝试围绕遗留代码进行测试时,这可能尤其成问题。在这种情况下,我经常发现自己使用链接器在测试中使用存根函数。


当人们谈论接缝时,这就是人们所指的。在C中,您唯一的选择就是使用预处理器或链接器来模拟您的依赖项。


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


#include "myimplementationfile.c"
#include 

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

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


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


至于交叉编译代码,为了使它工作,你需要在目标上有良好的设施。我已经通过googletest交叉编译到PowerPC架构上的Linux来完成此操作。这是有道理的,因为你有一个完整的shell和os来收集你的结果。对于不太丰富的环境(我将其归类为没有完整操作系统的任何东西),您应该只在主机上构建和运行。无论如何,您应该这样做,以便您可以作为构建的一部分自动运行测试。


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


Michael Feathers有一本关于测试遗留代码的优秀书籍。在一章中,他介绍了处理非面向对象代码的技巧,我强烈推荐。


编辑:我写了一篇关于单元测试程序代码的博客文章,GitHub上提供了源代码。[25] [26]


编辑:实用程序员出版了一本新书,专门针对我强烈推荐的单元测试C代码。[27] [28]

其它参考2


Minunit是一个非常简单的单元测试框架。
我用它来为avr。[29]测试c单片机代码。

其它参考3


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


http://cutest.sourceforge.net/[30]


它非常适合嵌入式系统,因为它非常轻巧和简单。我没有遇到让它在目标平台和桌面上工作的问题。除了编写单元测试之外,所需要的只是:



  • 包含头文件的地方
    你正在调用CuTest例程

  • 一个额外的C文件
    编译/链接到图像

  • 添加到main的一些简单代码
    设置并调用单元测试 - 我
    只需要一个特殊的主()
    如果被编译的函数
    UNITTEST是在期间定义的
    建立。



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


通过明智地使用externC{}块,它也支持测试C ++就好了。

其它参考4


我说和ratkok几乎一样但是如果你对单元测试有一个嵌入式扭曲那么......


Unity - 强烈建议的单元测试C代码框架。 [31]


本线程中提到的用于嵌入式C的TDD中的示例是使用Unity(和CppUTest)编写的。 [32]

其它参考5


您还可以查看libtap,这是一个输出Test Anything Protocol(TAP)的C测试框架,因此可以很好地集成到该技术的各种工具中。它主要用于动态语言世界,但它易于使用并且变得非常受欢迎。[33]


一个例子:


#include 

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

其它参考6


有一个优雅的C单元测试框架,支持名为cmocka的模拟对象。它只需要标准的C库,适用于各种计算平台(包括嵌入式)和不同的编译器。[34]


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


cmocka已经创建,也可以在嵌入式平台上运行,并且还支持Windows。


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


#include 
#include 
#include 
#include 

/* 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已完整记录,并且有几个示例是源代码的一部分。[35]


要开始使用cmocka,您应该阅读LWN.net上的文章:使用C [36]中的模拟对象进行单元测试


cmocka 1.0已于2015年2月发布。

其它参考7


在我开始寻找一种模拟函数的方法之前,我没有远远地测试遗留的C应用程序。我需要嘲笑我要隔离我想要测试的C文件。我尝试了cmock,我想我会采用它。


Cmock扫描头文件并根据它找到的原型生成模拟函数。模拟将允许您完美隔离测试C文件。您所要做的就是将测试文件与模拟而不是真实的目标文件链接起来。


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


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


最后一步是为统一的测试生成一个运行器。 Cmock与统一测试框架联系在一起。 Unity与任何其他单元测试框架一样容易亚博2018平台。


值得一试,很容易掌握:


http://sourceforge.net/apps/trac/cmock/wiki[37]


更新1


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


http://code.google.com/p/cmockery/[38]


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


它需要更多的手动工作来设置模拟,因为它不会生成代码。这并不代表现有项目的大量工作,因为原型不会发生太大变化:一旦你有了你的模拟,你就不需要改变它们一段时间(这是我的情况)。额外的打字可以完全控制嘲笑。如果有什么你不喜欢,你只需改变你的模拟。


不需要特殊的测试跑步者。您只需要创建一个测试数组并将其传递给run_tests函数。这里也有一些手工工作但我绝对喜欢自包含自治框架的想法。


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


整体Cmockery需要更多地了解模拟才能开始。例子可以帮助你克服这一点。看起来它可以用更简单的机制来完成工作。

其它参考8


作为C新手,我发现在C 中称为测试驱动开发的幻灯片非常有帮助。基本上,它使用标准assert()&&来传递消息,而没有任何外部依赖性。如果有人习惯了完整的堆栈测试框架,那么可能不会这样做:)[39]

其它参考9


CUnit [40]


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


并且 Tessy 自动化嵌入式软件的单元测试。[42]

其它参考10


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


有一些与C相关的依赖注入相关的技术,我在其他任何地方都看不到。

其它参考11


我们写了CHEAT(托管在GitHub上),以便于实用性和可移植性。[43] [44]


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


#include 

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


我没有使用框架,我只是使用autotools检查目标支持。实现main并使用assert(s)。


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


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

其它参考13


CppUTest - 强烈建议的单元测试C代码框架。 [45]


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

其它参考14


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


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


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


CxxTest用户指南
CxxTest Wiki [48] [49]

其它参考15


除了我明显的偏见


http://code.google.com/p/seatest/[50]


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

其它参考16


读完Minunit之后,我认为更好的方法是在断言宏中进行测试,我使用的很像防御程序技术。所以我使用了Minunit与标准断言相同的想法。你可以在k0ga的博客[51]中看到我的框架(一个好名字可能是NoMinunit)

其它参考17


cmockery在http://code.google.com/p/cmockery/[52]

其它参考18


Google拥有出色的测试框架。 http://code.google.com/p/googletest/wiki/GoogleTestPrimer[53]


是的,据我所知它可以使用普通的C,即不需要C ++特性(可能需要C ++编译器,不确定)。

其它参考19


Cmockery是最近推出的Google Code项目,它包含一个非常简单易用的C库,用于编写单元测试。[54]

其它参考20


首先,请看这里:http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C [55]


我公司有一个客户使用的C库。我们使用CxxTest(一个C ++单元测试库)来测试代码。 CppUnit也会起作用。如果你被卡在C中,我会推荐RCUNIT(但CUnit也是好的)。

其它参考21


如果您熟悉JUnit,那么我推荐CppUnit。
http://cppunit.sourceforge.net/cppunit-wiki[56]


假设你有c ++编译器来进行单元测试。如果没有,那么我必须同意亚当罗森菲尔德,检查是你想要的。

其它参考22


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

其它参考23


尝试lcut! - http://code.google.com/p/lcut[58]

其它参考24


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


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

其它参考25


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


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

其它参考26


我很惊讶没有人提到Cutter(http://cutter.sourceforge.net/)
你可以测试C和C ++,它与autotools无缝集成,并提供了一个非常好的教程。[60]

其它参考27


API Sanity Checker - C/C ++库的测试框架:[61]



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

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



例子:



  • fontconfig 2.8.0测试套件

  • FreeType 2.4.8的测试套件


其它参考28


如果你的目标是Win32平台或NT内核模式,你应该看一下cfix。[62] [63] [64]

其它参考29


如果你还在寻找测试框架,那么CUnitWin32就是Win32/NT平台的一个。[65]


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