第二章 测试框架入门:CUnit框架介绍、安装与配置、第一个测试用例
好,咱们进入正题。上一章聊了聊单元测试到底是个啥,为什么值得你花时间。这一章,咱们直接上手——用CUnit这个框架,写你的第一个测试用例。
说实话,我刚开始接触单元测试的时候,也看过不少框架。什么Check、Unity、Criterion,各有各的脾气。但CUnit是我个人用得最顺手的一个。为什么?因为它够简单,够直观,而且文档写得还算清楚。你想想看,咱们搞嵌入式的人,时间本来就紧,哪有功夫去研究那些花里胡哨的框架配置?
2.1 CUnit是什么?
CUnit,说白了就是一个轻量级的C语言单元测试框架。它跟JUnit是亲戚——设计思路很像,都是把测试用例组织成“套件(Suite)”,然后批量跑。
它的核心概念其实就三个:
- 测试注册表(Test Registry):管理所有测试套件的地方。就像一个目录,告诉你有哪些测试可以跑。
- 测试套件(Test Suite):一组相关的测试用例的集合。比如你写了一个计算器模块,那加减乘除的测试就可以放在一个套件里。
- 测试用例(Test Case):最小的测试单元。一个用例只测一件事,比如“测试加法函数输入2+3是否等于5”。
嗯,这里要注意:CUnit支持两种运行模式——自动模式和交互模式。自动模式适合集成到构建脚本里,跑完就出结果;交互模式适合调试时用,可以一步步看。我个人习惯用自动模式,省事。
2.2 安装与配置
安装CUnit其实不复杂,但不同平台有点小区别。我分别说一下。
Linux环境(Ubuntu/Debian)
如果你用的是Ubuntu,直接一条命令搞定:
sudo apt-get install libcunit1 libcunit1-dev
装完之后,头文件在 /usr/include/CUnit/,库文件在 /usr/lib/x86_64-linux-gnu/。编译的时候记得链接 -lcunit。
举个例子,编译命令长这样:
gcc -o test_calc test_calc.c -lcunit
Windows环境(MinGW)
Windows下稍微麻烦一点。我建议用MinGW或者Cygwin。如果你用MinGW,可以去CUnit官网下载源码包,然后手动编译:
./configure --prefix=/usr/local
make
make install
我曾经在Windows上折腾过一整个下午,就为了把CUnit跑起来。后来发现其实有个更简单的办法——直接用vcpkg:
vcpkg install cunit
嗯,省心多了。
验证安装
装完之后,你可以写个最简单的程序验证一下:
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
int main() {
if (CU_initialize_registry() != CUE_SUCCESS) {
return CU_get_error();
}
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
return 0;
}
如果能编译通过并运行,说明安装成功了。
2.3 第一个测试用例
好,重头戏来了。咱们写一个真正的测试用例。假设你有一个计算器模块,里面有个加法函数:
// calc.h
#ifndef CALC_H
#define CALC_H
int add(int a, int b);
#endif
// calc.c
#include "calc.h"
int add(int a, int b) {
return a + b;
}
现在,我们来写测试代码。完整的测试文件长这样:
// test_calc.c
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
#include "calc.h"
// 测试用例1:正数相加
void test_add_positive(void) {
CU_ASSERT(add(2, 3) == 5);
CU_ASSERT(add(0, 5) == 5);
CU_ASSERT(add(100, 200) == 300);
}
// 测试用例2:负数相加
void test_add_negative(void) {
CU_ASSERT(add(-2, -3) == -5);
CU_ASSERT(add(-5, 0) == -5);
}
// 测试用例3:边界值
void test_add_boundary(void) {
CU_ASSERT(add(INT_MAX, 0) == INT_MAX);
CU_ASSERT(add(INT_MIN, 0) == INT_MIN);
}
int main() {
// 初始化测试注册表
if (CU_initialize_registry() != CUE_SUCCESS) {
return CU_get_error();
}
// 创建测试套件
CU_pSuite pSuite = CU_add_suite("Suite_Calculator", NULL, NULL);
if (pSuite == NULL) {
CU_cleanup_registry();
return CU_get_error();
}
// 向套件中添加测试用例
if (CU_add_test(pSuite, "test of add positive", test_add_positive) == NULL) {
CU_cleanup_registry();
return CU_get_error();
}
if (CU_add_test(pSuite, "test of add negative", test_add_negative) == NULL) {
CU_cleanup_registry();
return CU_get_error();
}
if (CU_add_test(pSuite, "test of add boundary", test_add_boundary) == NULL) {
CU_cleanup_registry();
return CU_get_error();
}
// 运行测试
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
return 0;
}
编译运行:
gcc -o test_calc test_calc.c calc.c -lcunit
./test_calc
如果一切顺利,你会看到类似这样的输出:
CUnit - A unit testing framework for C - Version 2.1-3
http://cunit.sourceforge.net/
Suite: Suite_Calculator
Test: test of add positive ... passed
Test: test of add negative ... passed
Test: test of add boundary ... passed
--Run Summary: Type Total Ran Passed Failed
suites 1 1 n/a 0
tests 3 3 3 0
asserts 7 7 7 0
看到“passed”三个字,心里是不是踏实多了?
核心要点:一个测试用例只测一件事。别想着一个函数测所有情况,拆开写,出了问题一眼就能定位。
2.4 常用断言宏
CUnit提供了一堆断言宏,我挑几个最常用的列出来:
| 宏 | 作用 |
|---|---|
CU_ASSERT(expr) |
判断表达式是否为真 |
CU_ASSERT_EQUAL(actual, expected) |
判断两个值是否相等 |
CU_ASSERT_NOT_EQUAL(actual, expected) |
判断两个值是否不相等 |
CU_ASSERT_PTR_NULL(ptr) |
判断指针是否为NULL |
CU_ASSERT_PTR_NOT_NULL(ptr) |
判断指针是否不为NULL |
CU_ASSERT_STRING_EQUAL(str1, str2) |
判断两个字符串是否相等 |
我个人用得最多的是 CU_ASSERT_EQUAL 和 CU_ASSERT。前者适合数值比较,后者适合布尔判断。记住一点:断言失败时,CUnit会继续执行当前用例中的后续断言,不会立即终止。这一点跟某些框架不一样,你得心里有数。
小技巧:如果你想让测试在第一个断言失败时就停下来,可以用 CU_ASSERT_FATAL 系列宏。不过一般情况下,我建议让测试跑完,这样你能看到所有失败点。
2.5 避坑指南
嗯,这里说几个我踩过的坑:
- 忘记初始化注册表:
CU_initialize_registry()必须在所有操作之前调用。我曾经忘了这步,结果程序直接崩溃,查了半天才发现。 - 套件名和用例名重复:CUnit内部用名字来标识套件和用例,如果重名,后面的会覆盖前面的。所以命名时最好有点规律,比如“Suite_模块名”、“test_函数名_场景”。
- 链接顺序问题:编译时
-lcunit要放在源文件后面。有些编译器对链接顺序敏感,放前面可能报未定义引用。
警告:不要在测试用例里写 printf 来调试!用断言就够了。如果非要用,记得在正式提交前删掉,否则输出会乱成一团。
2.6 知识体系图
为了让你对整个流程有个直观的认识,我画了一张图:
这张图把整个流程串起来了。你写测试的时候,就按这个顺序来:初始化注册表 → 创建套件 → 添加用例 → 写断言 → 跑测试看报告。一步都不能少。
2.7 小结
这一章咱们把CUnit的安装、配置和第一个测试用例都过了一遍。说实话,写测试用例这件事,刚开始会觉得有点麻烦——明明代码跑得好好的,干嘛要多写一堆测试代码?但等你真正遇到那种“改了一行代码,炸了三个模块”的情况时,你就会感谢当初写了测试的自己。
下一章,咱们会深入聊聊测试用例的设计技巧,包括怎么划分测试粒度、怎么处理依赖关系。嗯,到时候见。