今年夏天,我开发了一个用纯c语言编写的嵌入式系统。这是我所在公司接管的一个现有项目。我已经非常习惯使用JUnit在Java中编写单元测试,但不知道为现有代码(需要重构)和添加到系统中的新代码编写单元测试的最佳方法是什么。
有什么项目可以让单元测试纯C代码像使用JUnit测试Java代码一样简单吗?任何特别适用于嵌入式开发(交叉编译到arm-linux平台)的见解都将非常感谢。
今年夏天,我开发了一个用纯c语言编写的嵌入式系统。这是我所在公司接管的一个现有项目。我已经非常习惯使用JUnit在Java中编写单元测试,但不知道为现有代码(需要重构)和添加到系统中的新代码编写单元测试的最佳方法是什么。
有什么项目可以让单元测试纯C代码像使用JUnit测试Java代码一样简单吗?任何特别适用于嵌入式开发(交叉编译到arm-linux平台)的见解都将非常感谢。
当前回答
Michael Feather的书《有效地使用遗留代码》介绍了很多专门用于C开发期间单元测试的技术。
有一些与依赖注入相关的技术是特定于C语言的,我在其他任何地方都没有见过。
其他回答
这是CUnit
嵌入式单元是嵌入式C系统的单元测试框架。它的设计抄袭了JUnit和CUnit等,然后为嵌入式C系统做了一些调整。嵌入式单元不需要标准C库。所有对象都分配到const区域。
Tessy自动化了嵌入式软件的单元测试。
我目前正在使用CuTest单元测试框架:
http://cutest.sourceforge.net/
它是嵌入式系统的理想选择,因为它非常轻量级和简单。让它在目标平台和桌面上运行都没有问题。除了编写单元测试,所需要的是:
头文件包含在任何地方 你称之为最可爱的程序 一个额外的“C”文件 编译/链接到映像 一些简单的代码添加到主要到 设置并调用单元测试- I 只需要把它放在一个特殊的main()中 函数,如果 过程中定义UNITTEST 构建。
系统需要支持堆和一些stdio功能(不是所有的嵌入式系统都有)。但是代码非常简单,如果您的平台没有这些需求,您可能可以使用这些需求的替代方案。
通过明智地使用extern“C”{}块,它还可以很好地支持测试c++。
谷歌拥有优秀的测试框架。https://github.com/google/googletest/blob/master/googletest/docs/primer.md
是的,据我所知,它将与普通C一起工作,即不需要c++功能(可能需要c++编译器,不确定)。
我个人喜欢谷歌测试框架。
测试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并给出一个空的实现。这意味着测试文件独立于其余代码库进行编译和链接,并独立执行。
至于交叉编译代码,您需要在目标上使用良好的工具。我使用googletest交叉编译到PowerPC体系结构上的Linux来实现这一点。这是有意义的,因为您有一个完整的shell和os来收集结果。对于不太丰富的环境(我将其归类为没有完整操作系统的任何环境),您应该只在主机上构建和运行。无论如何您都应该这样做,这样您就可以作为构建的一部分自动运行测试。
我发现测试c++代码通常要容易得多,因为OO代码通常比过程性代码耦合得少(当然这在很大程度上取决于编码风格)。此外,在c++中,你可以使用依赖注入和方法重写等技巧来将接缝插入封装的代码中。
Michael Feathers有一本关于测试遗留代码的优秀书籍。在其中一章中,他介绍了处理非oo代码的技巧,我强烈推荐这些技巧。
编辑:我写了一篇关于单元测试过程代码的博客文章,源代码可以在GitHub上找到。
编辑:Pragmatic Programmers出版了一本新书,专门介绍了C代码的单元测试,我强烈推荐这本书。
我编写Libcut只是出于对现有C单元测试库的不满。它具有原语的自动类型字符串(不需要test_eq_int, test_eq_long, test_eq_short,等等…)对于原语和字符串只有两个不同的集合),并且由一个头文件组成。这里有一个简短的例子:
#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有效。