今年夏天,我用直接C编写了一个嵌入式系统.这是我工作的公司接管的现有项目.我已经习惯于使用JUnit在Java中编写单元测试,但是对于为现有代码(需要重构)编写单元测试的最佳方法以及添加到系统中的新代码感到茫然.
有没有办法让单元测试普通的C代码像使用JUnit进行单元测试Java代码一样简单?任何专门针对嵌入式开发(交叉编译到arm-linux平台)的见解都将非常感激.
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
一组最小的宏,就是这样!关键是要表明对代码进行单元测试是多么容易.请参阅MinUnit主页.
安藤先生的CUnit
一个相当新的CUnit实现,显然仍处于早期开发阶段.请参阅安藤先生主页的CUnit.
此列表最后更新于2008年3月.
CMocka是C的测试框架,支持模拟对象.它易于使用和设置.
请参阅CMocka主页.
Criterion是一个跨平台的C单元测试框架,支持自动测试注册,参数化测试,理论,并可输出多种格式,包括TAP和JUnit XML.每个测试都在自己的过程中运行,因此可以根据需要报告或测试信号和崩溃.
有关详细信息,请参阅Criterion主页.
HWUT是一个通用的单元测试工具,对C有很好的支持.它可以帮助创建Makefile,生成在最小"迭代表"中编码的大量测试用例,沿着状态机走,生成C-stub等等.一般方法非常独特:Verdicts基于'良好的stdout/bad stdout'.但是,比较功能是灵活的.因此,可以使用任何类型的脚本进行检查.它可以应用于任何可以产生标准输出的语言.
请参见HWUT主页.
适用于C和C++的现代,可移植,跨语言的单元测试和模拟框架.它提供了一个可选的BDD表示法,一个模拟库,能够在一个进程中运行它(使调试更容易).可以自动发现测试功能的测试运行器.但您可以通过编程方式创建自己的.
所有这些功能(以及更多)都在CGreen手册中进行了解释.
Wikipedia在单元测试框架列表下提供了C单元测试框架的详细列表:C
我个人喜欢Google Test框架.
测试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上提供了源代码.
编辑:实用程序员出版了一本专门针对单元测试C代码的新书,我强烈推荐.
Minunit是一个非常简单的单元测试框架.我用它来测试avr的微控制器代码.
我目前正在使用CuTest单元测试框架:
http://cutest.sourceforge.net/
它非常适合嵌入式系统,因为它非常轻巧和简单.我没有遇到让它在目标平台和桌面上工作的问题.除了编写单元测试外,所需要的只是:
在您调用CuTest例程的任何位置都包含头文件
要编译/链接到图像中的单个附加"C"文件
一些简单的代码添加到main来设置和调用单元测试 - 我只是在一个特殊的main()函数中,如果在构建过程中定义了UNITTEST,它就会被编译.
系统需要支持堆和一些stdio功能(并非所有嵌入式系统都具有).但是代码很简单,如果您的平台没有这些代码,您可能可以替代这些需求.
通过明智地使用extern"C"{}块,它也支持测试C++.
我说和ratkok几乎一样但是如果你对单元测试有一个嵌入式扭曲那么......
Unity - 强烈建议的单元测试C代码框架.
本线程中提到的嵌入式C TDD中的示例是使用Unity(和CppUTest)编写的.
您还可以查看libtap,这是一个C测试框架,它输出Test Anything Protocol(TAP),因此可以很好地集成到该技术的各种工具中.它主要用于动态语言世界,但它易于使用并且变得非常流行.
一个例子:
#includeint 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库,适用于各种计算平台(包括嵌入式)和不同的编译器.
它还支持不同的消息输出格式,如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是完全记录和几个例子是源代码的一部分.
要开始使用cmocka,您应该阅读LWN.net上的文章:在C中使用模拟对象进行单元测试
cmocka 1.0已于2015年2月发布.
在开始寻找模拟函数的方法之前,我没有对遗留的C应用程序进行远程测试.我非常需要模拟来隔离我想要测试的C文件.我尝试了一下cmock,我想我会采用它.
Cmock扫描头文件并根据它找到的原型生成模拟函数.模拟将允许您完美隔离测试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需要更多地了解模拟才能开始.示例可以帮助您克服这个问题.看起来它可以用更简单的机制来完成工作.
作为C新手,我发现在C中称为测试驱动开发的幻灯片非常有用.基本上,它使用标准assert()
一起&&
传递消息,没有任何外部依赖性.如果有人习惯了完整的堆栈测试框架,这可能不会做:)
我们写了CHEAT(托管在GitHub上),以便于实用性和可移植性.
它没有依赖关系,不需要安装或配置.只需要头文件和测试用例.
#includeCHEAT_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
它也有漂亮的颜色.
有CUnit
而嵌入式设备是嵌入式C系统的单元测试框架.它的设计是从JUnit和CUnit等复制而来,然后在某种程度上适用于嵌入式C系统.嵌入式单元不需要std C libs.所有对象都分配给const区域.
而苔丝自动化嵌入式软件的单元测试.
我不使用框架,我只是使用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
Michael Feather的书"有效地使用遗留代码"提供了许多特定于C开发期间单元测试的技术.
有一些与C相关的依赖注入相关的技术,我还没有在其他任何地方看到过.
CppUTest - 强烈建议的单元测试C代码框架.
本线程中提到的用于嵌入式C的TDD中的示例是使用CppUTest编写的.
我将CxxTest用于嵌入式c/c ++环境(主要是C++).
我更喜欢CxxTest,因为它有一个perl/python脚本来构建测试运行器.经过一个小坡度来设置它(因为你不必编写测试运行器,因此更小),它非常容易使用(包括样本和有用的文档).最大的工作是设置代码访问的"硬件",这样我就可以有效地进行单元/模块测试.之后,很容易添加新的单元测试用例.
如前所述,它是一个C/C++单元测试框架.所以你需要一个C++编译器.
CxxTest用户指南 CxxTest Wiki
除了我明显的偏见
http://code.google.com/p/seatest/
是一个很好的简单方法来单元测试C代码.模仿xUnit
读完Minunit之后,我认为更好的方法是在断言宏中进行测试,我使用的很像防御程序技术.所以我使用了Minunit与标准断言相同的想法.您可以在k0ga的博客中看到我的框架(一个好名字可能是NoMinunit)