今年夏天,我用直接 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 手册中进行了解释。
我个人喜欢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 中并给出一个空实现。这意味着测试文件独立于代码库的其余部分进行编译和链接并单独执行。
至于交叉编译代码,为了使它工作,你需要在目标上有良好的设施。我已经通过 googletest 交叉编译到 PowerPC 架构上的 Linux 来完成此操作。这是有道理的,因为你有一个完整的 shell 和 os 来收集你的结果。对于不太丰富的环境(我将其归类为没有完整操作系统的任何东西),您应该只在主机上构建和运行。无论如何你应该这样做,这样你就可以在构建过程中自动运行测试。
我发现测试 C ++ 代码通常要容易得多,因为 OO 代码通常比程序更少耦合(当然这在很大程度上取决于编码风格)。同样在 C ++ 中,您可以使用依赖注入和方法覆盖等技巧将接缝转换为以其他方式封装的代码。
Michael Feathers 有一本关于测试遗留代码的优秀书籍 。在一章中,他介绍了处理非面向对象代码的技巧,我强烈推荐。
编辑 :我写了一篇关于单元测试程序代码的博客文章 , 在 GitHub 上提供了源代码。
Minunit是一个非常简单的单元测试框架。我用它来测试 avr 的微控制器代码。