也许单元测试最关键的元素是测试框架。在这篇博文中,我们将讨论在嵌入式开发中C语言的各种选择,并简要分析利弊。
考虑因素
Test Runners
大多数嵌入式编程使用C,下面讨论的所有框架都是针对C或C++的。这本身会带来一些困难。许多单元测试框架利用了被称为“自省”的语言特性。实际上,这是在运行时确定正在执行的程序的结构信息的能力。它对于向框架自我注册的测试非常有用,并广泛用于面向Ruby、Python和Java等语言的框架。
实际上,这意味着C中的测试框架需要程序员做更多的设置工作。大多数框架需要一个手动编写的“runner”来调用测试套件,这可能会导致错误,例如忘记向runner添加特定的测试。有一些工具可以帮助自动化这个过程,但是它们不在严格的“测试框架”的范围之内——它们本身更类似于完整的构建系统。
C++有一些允许自省的特性,所以测试该语言的套件能实现自动测试注册。在大多数情况下,将完全用C编写的项目编译和测试为C++是可能的。这通常是一个好的解决方案,但是对于不习惯这种语言的嵌入式开发人员来说可能会不太舒服。
在硬件上运行
有一篇文章,讨论了运行测试的最佳位置。它列出了三个不同的选项:通过模拟器的开发系统、本地工作站或系统硬件。通常,使用前两个选项中的一个是有意义的,但在某些情况下(或不同的测试),使用其他选项可能有意义。
话虽如此,测试框架的选择将影响到是否可以执行这些操作。根据你平台可用的模拟器类型,可能很难使用依赖于在操作系统下运行的工具(如check))。这肯定不会对裸金属硬件本身起作用。另一方面,当测试失败可能导致分段错误时,具有在单独的地址空间中运行测试的功能是非常好的。
框架选项
Unity
这个测试框架很轻,非常便于携带。开发人员声称,它可以移植到任何架构上,从高性能的x86机器到8位微控制器,包括具有神秘功能的架构,如奇怪的整数宽度。它专门针对嵌入式开发。
它的缺点是缺乏高级功能,如地址空间隔离,或自动运行程序生成和测试集成。它与ThrowTheSwitch工具套件集成得很好,其中包括CMock、CException以及Ceedling。当一起使用时,Unity的简单性的许多缺点被减轻了。
Check
check是另一个相对轻量级的C单元测试框架。与Unity不同,相反,它专注于安全和简单易用。它出名的主要原因是它在一个隔离的地址空间中运行每个测试,这意味着可以捕获无效的内存访问。相比之下,使用Unity的测试会因为分段错误而彻底失败。
它还有一些比Unity更方便的功能,包括参数化测试,可以方便地编写应该在一系列不同值上验证输出的例程。它在设计时没有考虑裸机嵌入式开发。
check也有缺点。首先,很明显,是对操作系统的依赖,这意味着直接移植到嵌入式系统非常困难,并且在模拟器下交叉编译运行时可能会有问题。
其次,生成子进程比简单的函数调用要昂贵得多,所以当check必须运行许多测试时,它往往会停滞不前。
像任何其他纯C框架一样,测试运行程序必须手工编写。
GoogleTest
GoogleTest是Google的单元测试框架。它是用C++编写的,但是稍加调整就可以用来测试C应用程序。它支持使用Google Mock,这对于测试依赖于硬件的模块是必要的。
GoogleTest确实支持在单独的地址空间中运行测试,但默认情况下并不这样做。这些测试被称为“死亡测试”,它们主要用于捕捉预期的故障,而不是对意外segfaults等的保护。
这个测试框架的文件非常好,嵌入式开发人员值得一看。