917

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

是否有任何项目可以使单元测试纯 C 代码像使用 JUnit 单元测试 Java 代码一样简单?任何特别适用于嵌入式开发(交叉编译到 arm-linux 平台)的见解将不胜感激。

4

31 回答 31

537

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

王牌单位

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

GNU 自动单元

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

cUnit

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

单元

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

酷测

一个简单的框架,只有一个 .c 和一个 .h 文件,您可以放入源代码树。请参阅CutTest 主页

CppUnit

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

embUnit

embUnit(Embedded Unit)是另一个嵌入式系统的单元测试框架。这个似乎已被 AceUnit 取代。嵌入式单元主页

最小单位

一组最小的宏,就是这样!重点是展示对代码进行单元测试是多么容易。请参阅MinUnit 主页

安藤先生的CUnit

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

该列表最后一次更新是在 2008 年 3 月。

更多框架:

西莫卡

CMocka 是一个支持模拟对象的 C 测试框架。它易于使用和设置。

请参阅CMocka 主页

标准

Criterion 是一个跨平台的 C 单元测试框架,支持自动测试注册、参数化测试、理论,可以输出多种格式,包括 TAP 和 JUnit XML。每个测试都在自己的进程中运行,因此可以根据需要报告或测试信号和崩溃。

有关详细信息,请参阅标准主页

武汉理工大学

HWUT 是一个通用的单元测试工具,对 C 有很好的支持。它可以帮助创建 Makefile、生成以最小“迭代表”编码的大量测试用例、遍历状态机、生成 C-stub 等等。一般方法非常独特:判断基于“好标准输出/坏标准输出”。但是,比较功能是灵活的。因此,任何类型的脚本都可以用于检查。它可以应用于任何可以产生标准输出的语言。

请参阅HWUT 主页

绿

用于 C 和 C++ 的现代、可移植、跨语言单元测试和模拟框架。它提供了一个可选的 BDD 表示法、一个模拟库以及在单个进程中运行它的能力(使调试更容易)。可以使用自动发现测试功能的测试运行器。但是您可以通过编程方式创建自己的。

所有这些功能(以及更多)都在CGreen 手册中进行了解释。

维基百科在单元测试框架列表下给出了 C 单元测试框架的详细列表:C

于 2008-09-15T19:14:36.837 回答
178

我个人喜欢Google 测试框架

测试 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 来完成此操作。这是有道理的,因为你有一个完整的外壳和操作系统来收集你的结果。对于不太丰富的环境(我将其归类为没有完整操作系统的任何环境),您应该只在主机上构建和运行。无论如何,您都应该这样做,这样您就可以在构建过程中自动运行测试。

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

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

编辑:我写了一篇关于单元测试程序代码的博客文章,源代码在 GitHub 上可用

编辑Pragmatic Programmers 出版了一本新书,专门解决了我强烈推荐的单元测试 C 代码。

于 2009-09-11T10:19:22.913 回答
143

Minunit是一个非常简单的单元测试框架。我正在使用它对 avr 的 c 微控制器代码进行单元测试。

于 2008-09-15T19:27:50.893 回答
46

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

Unity - 强烈推荐用于单元测试 C 代码的框架。

#include <unity.h>

void test_true_should_be_true(void)
{
    TEST_ASSERT_TRUE(true);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_true_should_be_true);
    return UNITY_END();
}

该线程TDD for Embedded C中提到的书中示例是使用 Unity(和 CppUTest)编写的。

于 2011-11-08T08:40:21.147 回答
43

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

http://cutest.sourceforge.net/

它非常适合嵌入式系统,因为它非常轻巧且简单。我可以毫无问题地让它在目标平台和桌面上工作。除了编写单元测试之外,还需要:

  • 无论您在何处调用 CutTest 例程,都包含一个头文件
  • 要编译/链接到图像中的单个附加“C”文件
  • 一些简单的代码添加到 main 以设置和调用单元测试 - 如果在构建期间定义了 UNITTEST,我只是将它放在一个特殊的 main() 函数中。

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

通过对 extern "C"{} 块的一些明智使用,它还支持很好地测试 C++。

于 2008-09-16T18:00:38.300 回答
37

您可能还想看看libtap,这是一个 C 测试框架,它输出测试任何协议 (TAP),因此可以很好地与针对该技术推出的各种工具集成。它主要用于动态语言世界,但它易于使用并且非常流行。

一个例子:

#include <tap.h>

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();
}
于 2008-09-16T06:32:12.747 回答
27

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

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

cmocka 的创建也可以在嵌入式平台上工作,并且还支持 Windows。

一个简单的测试如下所示:

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

/* 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 上的文章:Unit testing with mock objects in C

cmocka 1.0 已于 2015 年 2 月发布。

于 2013-01-20T13:43:02.243 回答
21

在我开始寻找模拟函数的方法之前,我并没有对遗留的 C 应用程序进行太多测试。我非常需要模拟来将我想要测试的 C 文件与其他文件隔离开来。我给了 cmock 一个尝试,我想我会采用它。

Cmock 扫描头文件并根据它找到的原型生成模拟函数。Mocks 将允许您在完全隔离的情况下测试 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 需要对 mock 有更多了解才能开始。示例应该可以帮助您克服这一点。看起来它可以用更简单的机制来完成这项工作。

于 2010-07-22T13:03:58.620 回答
18

我们编写CHEAT(托管在GitHub 上)是为了便于使用和移植。

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

#include <cheat.h>

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

它也有漂亮的颜色。

于 2014-10-03T17:14:06.830 回答
16

作为一个 C 新手,我发现名为C 中测试驱动开发的幻灯片非常有帮助。基本上,它使用标准assert()连同&&传递消息,没有任何外部依赖。如果有人习惯了全栈测试框架,这可能不会:)

于 2011-10-08T23:10:25.873 回答
12

CUnit

Embedded Unit是嵌入式C系统的单元测试框架。它的设计是从 JUnit 和 CUnit 等中复制而来的,然后在某种程度上适应了嵌入式 C 系统。嵌入式单元不需要标准 C 库。所有对象都分配到 const 区域。

Tessy自动化嵌入式软件的单元测试。

于 2008-09-15T19:27:28.523 回答
12

我不使用框架,我只是使用自动工具“检查”目标支持。实现“主要”并使用断言。

我的测试目录 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
于 2008-09-16T13:54:28.893 回答
12

Michael Feather 的书“Working Effectively with Legacy Code”介绍了许多特定于 C 开发期间单元测试的技术。

有一些与依赖注入相关的技术是特定于 C 的,我在其他任何地方都没有见过。

于 2009-09-11T19:21:05.457 回答
7

CppUTest - 强烈推荐的用于单元测试 C 代码的框架。

该线程TDD for Embedded C中提到的书中示例是使用 CppUTest 编写的。

于 2011-06-07T18:46:48.490 回答
6

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

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

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

CxxTest 用户指南 CxxTest Wiki

于 2008-09-15T22:47:08.993 回答
5

谷歌有优秀的测试框架。https://github.com/google/googletest/blob/master/googletest/docs/primer.md

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

于 2008-09-15T19:14:53.623 回答
5

除了我明显的偏见

http://code.google.com/p/seatest/

是一种对 C 代码进行单元测试的好方法。模仿 xUnit

于 2010-07-01T05:09:36.550 回答
5

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

于 2011-02-18T20:38:42.097 回答
4

http://code.google.com/p/cmockery/上的 cmockery

于 2008-09-15T19:22:11.000 回答
4

Cmockery是一个最近启动的项目,它包含一个非常简单易用的 C 库来编写单元测试。

于 2008-09-16T15:40:09.667 回答
3

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

我的公司有一个客户使用的 C 库。我们使用 CxxTest(一个 C++ 单元测试库)来测试代码。CppUnit 也可以工作。如果你被 C 困住,我会推荐 RCUNIT(但 CUnit 也很好)。

于 2008-09-15T20:39:24.840 回答
2

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

那是假设您有 c++ 编译器来进行单元测试。如果不是,那么我必须同意 Adam Rosenfield 的观点,即支票就是你想要的。

于 2008-09-15T19:22:11.903 回答
2

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

于 2008-11-25T11:12:08.900 回答
2

试试lcut!- http://code.google.com/p/lcut

于 2010-10-10T01:43:07.320 回答
2

API Sanity Checker — C/C++ 库的测试框架:

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

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

例子:

于 2011-01-25T14:57:02.473 回答
1

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

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

于 2008-09-15T19:22:34.727 回答
1

LibU ( http://koanlogic.com/libu ) 有一个单元测试模块,它允许明确的测试套件/用例依赖、测试隔离、并行执行和可定制的报告格式器(默认格式为 xml 和 txt)。

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

于 2010-04-16T08:55:58.313 回答
1

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

于 2013-01-22T14:48:20.437 回答
0

如果你的目标是 Win32 平台或 NT 内核模式,你应该看看cfix

于 2008-10-11T16:18:50.770 回答
0

如果您仍在寻找测试框架,CUnitWin32是针对 Win32/NT 平台的一种。

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

于 2009-01-12T00:30:39.430 回答
0

我只是出于对现有 C 单元测试库的挫败感而编写了Libcut 。它具有基元的自动类型字符串(不需要 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。

于 2015-01-04T22:33:43.540 回答