一、Unity Test 测试框架的使用
1.1 Unity Test 简介
Welcome to the Unity Test Project, one of the main projects of ThrowTheSwitch.org. Unity Test is a unit testing framework built for C, with a focus on working with embedded toolchains.
This project is made to test code targetting microcontrollers big and small. The core project is a single C file and a pair of headers, allowing it to be added to your existing build setup without too much headache.
Unity Test 是一个为 C 语言搭建的单元测试框架,主体包含一个 C 文件与两个头文件,通过引用这三个文件,就可以对我们现有的工程进行测试了~
1.2 Unity Test 文件说明
1.2.1 Unity Test 源码下载
【下载地址】: ThrowTheSwitch/Unity: Simple Unit Testing for C (github.com)
打开下载地址,点击 code
后,如下图
- 点击
Download ZIP
下载压缩包,解压即可使用(推荐,并建议保留压缩包作为副本) - 复制 HTTPS 链接,使用 Git 克隆 Unity 项目到本地(没有 github 账户的同学可以直接下载压缩包)
1.2.2 文件说明
docs
:一些帮助文档,其中 UnityAssertionsCheatSheetSuitableforPrintingandPossiblyFraming.pdf 列举了大部分断言结构,更加详尽的断言描述参考 UnityAssertionsReference.mdexamples
:测试用例,在 1.3 小结中将会通过其中的一个用例说明 Unity 的测试过程src
:Unity 的核心内容,包含一个 c 文件与两个 h 文件,包含一些函数及断言的实现
1.3 Unity Test 用例测试
1.3.1 测试工程搭建
任选一款可以编译、运行 C 代码的 IDE (如 Visual Studio) ,新建空工程
- 将 Unity\src 下的
unity.c
、unity.h
、unity_internals.h
拷贝到工程目录下 - 将 Unity\examples\example_1\src 下的
ProductionCode.c
、ProductionCode.h
拷贝到工程目录下 - 将 Unity\examples\example_1\test 下的
TestProductionCode.c
及其子目录 \test_runners 下的TestProductionCode_Runner.c
拷贝到工程目录下
至此测试文件准备完毕,新建工程的目录下应该有:
unity.c
、unity.h
、unity_internals.h
是 Unity Test 测试框架核心文件ProDuctionCode.c
是需要测试的功能集合,即各功能模块的代码实现,并在对应的 h 文件中进行声明。需要注意的是,h 文件需要增加宏定义,从而让头文件在工程中只被包含一次,也更符合编码规范TestProductionCode.c
是测试用例集合,测试用例实质上也是一些函数,通常以 test_ 开头,函数的内部是具体的断言实现,也可以在测试函数中编写测试代码,作为断言的输入TestProdutionCode_Runner.c
测试 mian 函数,执行测试用例并输出结果
可见,使用 Unity Test 框架进行单元测试时,工程主要包含以下 4 部分
- Unity Test 框架核心文件
- 被测试函数
- 测试用例
- main
【注】:若工程目录按 \src 与 \inc 分别存放了 c 与 h 文件,则在头文件包含时需要修改相对路径
1.3.2 测试用例执行
编译工程并运行,输出结果如下
一条测试用例的输出共四部分
- 测试用例文件名,在 main 函数起始处由 UnityBegin 函数配置,在用例执行过程中可以多次配置,主要用于打印输出以区分用例文件
- 用例所在行,当用例执行失败时可以快速定位用例位置(当用例 PASS 时,行号即 main 函数中执行测试用例时设定的行号;当用例 FAIL 时,行号即具体失败用例的行号)
- 用例名称
- 用例执行结果(PASS / FAIL)
1.3.3 相关说明
-
mian 主函数
//简单的 main 函数示例 int main(void) { UnityBegin(filename); RUN_TEST(test_TheFirst, x); RUN_TEST(test_TheSecond, y); RUN_TEST(test_TheThird, z); return UnityEnd(); }
-
UnityBegin(filename)
整个测试流程由 UnityBegin 函数开始, UnityBegin 入参为一个字符串常量,内容通常设置为后续用例所在的文件名,当用例按类别存放在不同的文件中时,函数入参会结合用例结果打印到控制台,方便区分不同类别的用例
-
RUN_TEST(test_TheFirst, x)
test_TheFirst 为用例函数,x 为用例函数所在的行号,该行号只在用例 PASS 时生效,并打印到控制台,当用例执行 FAIL 时,控制台将输出 FAIL 用例所在的行号
RUN_TEST 实质是一个宏,内部定义了一些如统计用例数量、执行用例、打印结果的功能,每条用例均会执行一次 RUN_TEST ,宏定义参考如下
#define RUN_TEST(TestFunc, TestLineNum) \ { \ Unity.CurrentTestName = #TestFunc; \ Unity.CurrentTestLineNumber = TestLineNum; \ Unity.NumberOfTests++; \ if (TEST_PROTECT()) \ { \ setUp(); \ TestFunc(); \ } \ if (TEST_PROTECT()) \ { \ tearDown(); \ } \ UnityConcludeTest(); \ }
-
UnityEnd()
输出保存在变量 Unity 中的汇总结果
调用 UnityBegin 会重置全局变量 Unity ,因此对于某个测试用例文件,UnityBegin 与 UnityEnd 总是成对出现的
值得注意的是,在 UnityBegin 函数前,仍然可以加入自己的代码,初始化全局变量,或调用一些函数来生成测试用例所需的原始数据,不过这些数据均需要通过全局变量传递
-
-
测试用例文件
-
setUp 函数
参考上文中的 RUN_TEST 宏, setUp 函数在每个用例函数前调用,因此可以利用该函数针对每一条用例作一些数据初始化或者运算,可按需求增加入参
-
tearDown 函数
tearDown 函数在每个用例函数完成后,或者由于用例 FAIL 导致用例函数异常退出后调用,用于做一些后续处理,可按需求增加入参
-
test_Func 测试用例函数
每个测试用例函数包含一个或多个测试用例,每个测试用例由具体的断言实现,根据断言类型,可能需要一个或多个入参,简单的测试用例函数参考如下
void test_Func(void) { TEST_ASSERT_EQUAL(0, MyTestFunc(x,y)); ... }
测试用例函数通常以 test_ 开始,上文用例函数中的 相等断言 需要两个入参,参数 1 为期望值,参数 2 为实际值,当两个值相等时,断言校验通过,即测试用例 PASS ,否则测试用例 FAIL 。期望值由我们预先设置,实际值为被测试函数的返回值,测试用例 PASS 即被测试函数的返回值符合预期
更多的断言类型参考
docs
中的 UnityAssertionsReference.md 文档
-
1.4 Unity Test 的实际应用
- 新建工程,并将 Unity Test 核心文件拷贝到工程路径内
- 编写目标函数文件(func.c & func.h),文件内包含各被测试函数
- 编写测试用例,该 c 文件需要包含 func.h 与 unity.h
- 编写 main 文件,执行用例并输出结果,该 c 文件需要包含 func.h 与 unity.h
- 编译运行