第3章:CUnit框架入门
好,咱们今天来聊聊CUnit。说实话,在嵌入式C的世界里,做单元测试的框架就那么几个。CUnit算是其中比较轻量、容易上手的一个。我个人习惯把它叫做「C语言界的JUnit」——虽然功能没那么花哨,但该有的都有了。
3.1 CUnit的安装与配置
先说说安装。这事儿其实不复杂,但我在项目中遇到过几次因为版本不对导致编译失败的情况,所以咱们一步步来。
3.1.1 下载与编译
CUnit的源码可以从SourceForge上拿到。你直接搜「CUnit SourceForge」就行。下载下来是个压缩包,解压后进入目录,标准的Linux三板斧:
./configure
make
sudo make install
嗯,这里要注意。如果你是在嵌入式Linux环境下,可能需要指定交叉编译工具链。比如:
./configure --host=arm-linux-gnueabihf --prefix=/usr/local/arm/cunit
为什么要指定prefix?说白了,就是不想让库文件污染系统目录。我一般习惯把第三方库都装到单独的目录里,方便管理。
3.1.2 验证安装
装完之后,怎么知道装好了?写个小程序试试:
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
int main() {
if (CU_initialize_registry() != CUE_SUCCESS) {
return CU_get_error();
}
CU_cleanup_registry();
return 0;
}
编译命令:
gcc -o test_cunit test_cunit.c -lcunit -lcunitbasic
如果能编译通过并正常运行,恭喜你,环境搭好了。
3.2 CUnit的基本结构:Suite与Test
理解了安装,咱们来看看CUnit的核心概念。说白了就两个东西:Suite和Test。
- Suite(测试套件):一组相关的测试用例的集合。比如你有个模块叫「温度传感器驱动」,那它的所有测试用例就放在一个Suite里。
- Test(测试用例):一个具体的测试函数。比如「测试温度读取函数在正常范围内是否返回正确值」。
你想想看,这就像整理文件。Suite是文件夹,Test是里面的文件。没有文件夹,文件就乱成一团;没有文件,文件夹就是个空壳。
3.2.1 Suite的创建与注册
在CUnit里,创建Suite的流程是这样的:
CU_pSuite pSuite = NULL;
// 第一步:初始化注册表
if (CUE_SUCCESS != CU_initialize_registry())
return CU_get_error();
// 第二步:创建Suite
pSuite = CU_add_suite("Suite_温度传感器", init_suite, clean_suite);
if (NULL == pSuite) {
CU_cleanup_registry();
return CU_get_error();
}
这里有个细节:init_suite和clean_suite是两个回调函数。它们分别在Suite开始前和结束后被调用。我一般用init_suite来分配资源、初始化硬件模拟,用clean_suite来释放资源。
3.2.2 Test的添加
有了Suite,接下来就是往里加Test:
CU_add_test(pSuite, "测试正常温度读取", test_temp_normal);
CU_add_test(pSuite, "测试温度上限溢出", test_temp_overflow);
CU_add_test(pSuite, "测试温度传感器断开", test_temp_disconnect);
每个测试函数都是一个普通的C函数,返回值必须是void,不接受参数:
void test_temp_normal(void) {
// 测试代码
}
3.3 编写第一个CUnit测试用例
光说不练假把式。咱们写一个完整的例子。假设我们有个简单的加法函数:
// 被测试的函数
int add(int a, int b) {
return a + b;
}
现在来写测试:
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
// 测试用例1:正数相加
void test_add_positive(void) {
CU_ASSERT(add(2, 3) == 5);
CU_ASSERT(add(100, 200) == 300);
}
// 测试用例2:负数相加
void test_add_negative(void) {
CU_ASSERT(add(-1, -2) == -3);
CU_ASSERT(add(-5, 3) == -2);
}
// 测试用例3:边界值
void test_add_boundary(void) {
CU_ASSERT(add(0, 0) == 0);
CU_ASSERT(add(INT_MAX, 0) == INT_MAX);
CU_ASSERT(add(INT_MIN, 0) == INT_MIN);
}
int main() {
// 初始化
if (CUE_SUCCESS != CU_initialize_registry())
return CU_get_error();
// 创建Suite
CU_pSuite pSuite = CU_add_suite("Suite_加法测试", NULL, NULL);
if (NULL == pSuite) {
CU_cleanup_registry();
return CU_get_error();
}
// 添加测试用例
CU_add_test(pSuite, "测试正数相加", test_add_positive);
CU_add_test(pSuite, "测试负数相加", test_add_negative);
CU_add_test(pSuite, "测试边界值", test_add_boundary);
// 运行测试
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
return 0;
}
这段代码里,我用了CU_ASSERT宏。它和标准C的assert类似,但CUnit会记录断言的结果,而不是直接崩溃。你想想看,如果测试一个挂一个,那调试起来得多痛苦?
3.4 运行测试并查看结果
代码写好了,怎么跑?编译运行就行:
gcc -o test_add test_add.c -lcunit -lcunitbasic
./test_add
输出结果大概长这样:
CUnit - A Unit testing framework for C - Version 2.1-3
http://cunit.sourceforge.net/
Suite: Suite_加法测试
Test: 测试正数相加 ... passed
Test: 测试负数相加 ... passed
Test: 测试边界值 ... 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」是不是很爽?但别高兴太早。我曾经在项目里遇到过测试全绿,结果上线后功能还是崩了的情况。为什么?因为测试用例覆盖不全。你想想看,如果只测了正数相加,没测溢出情况,那INT_MAX+1这种场景就漏掉了。
3.4.1 测试失败时的输出
如果某个测试失败了,CUnit会告诉你具体是哪一行:
Suite: Suite_加法测试
Test: 测试边界值 ... FAILED
1. test_add.c:23 - CU_ASSERT(add(INT_MAX, 1) == INT_MIN)
--Run Summary: Type Total Ran Passed Failed
suites 1 1 n/a 0
tests 3 3 2 1
asserts 7 7 6 1
看到第23行了吗?这就是CUnit的贴心之处——直接告诉你断言失败的位置。省得你一行行去翻代码。
3.5 小结
这一章咱们走了一遍CUnit的完整流程:从安装配置,到理解Suite和Test的概念,再到写第一个测试用例,最后看测试结果。说白了,CUnit就是个工具,真正重要的是你写测试用例的思路。
下一章咱们会深入聊聊CUnit的断言宏和测试夹具,这些东西在实际项目中特别有用。到时候我会分享一些我在嵌入式项目里踩过的坑,保证让你少走弯路。