尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Unity C 单元测试框架入门指南:从零创建、编译与运行你的第一个测试文件

Unity C 单元测试框架入门指南:从零创建、编译与运行你的第一个测试文件 测试嵌入式【免费下载链接】UnitySimple unit testing for C项目地址https://gitcode.com/gh_mirrors/un/Unity点击查看免费下载本文基于 Unity 官方入门文档UnityGettingStartedGuide.md并结合仓库源码unity.c、unity.h、unity_internals.h与官方示例example_1编写完整覆盖如何组织测试文件、setUp/tearDown 生命周期、RUN_TEST 的两种调用形态、TEST_IGNORE 忽略机制、TEST_PROTECT/TEST_ABORT 中止机制以及用 GCC 编译链接并运行测试可执行文件的完整流程。读完本文你将能够在自己的 C 项目中直接落地一套最小可用的 Unity 单元测试工程。Unity 是一个面向 C 语言的单元测试框架核心代码只有三个文件一个 C 文件unity.c和两个头文件unity.h、unity_internals.h。它们通过一组函数与宏为开发者提供易用的测试能力。Unity 从设计之初就强调跨平台它尽力遵循 C 标准同时为众多打擦边球的嵌入式编译器提供支持官方文档中提到的已使用编译器包括 GCC、IAR、Clang、Green Hills、Microchip 以及 MS Visual Studio将其移植到新的目标平台通常并不费力移植与裁剪的细节见 UnityConfigurationGuide.md。一、仓库目录速览哪些才是真正的 Unity通过 Git 之类的途径获取 Unity 后你会发现仓库里东西不少但请放心——Unity 本身非常小巧其余内容只是为了让你的使用更省心。按官方文档的划分仓库各部分职责如下目录/文件职责src核心代码所在包含一个 C 文件和两个头文件这三个文件就是 Unity 本体docs全部官方文档本文所在的入门指南也在此examples若干可直接运行的 Unity 使用示例example_1 ~ example_5extras可选扩展如 bdd、fixture、memory不属于核心项目testUnity 及其脚本自身的测试套件日常使用通常无需进入auto可选的 Ruby 辅助脚本用于简化测试工作流如测试运行器生成器Ruby 与这些脚本都不是使用 Unity 的必要条件从源码看unity.h 顶部定义UNITY_VERSION_MAJOR 2、UNITY_VERSION_MINOR 7、UNITY_VERSION_BUILD 1即当前仓库对应 Unity 2.7.1。文档体系导读官方文档中与本入门指南配套的还有几份入门后按需查阅UnityAssertionsReference.md断言Assertions全量参考是你日常使用最多的部分UnityAssertionsCheatSheetSuitableforPrintingandPossiblyFraming.pdf上述断言文档的精简速查表适合打印出来放在手边UnityConfigurationGuide.md针对新目标平台/新编译器时的配置参考UnityHelperScriptsGuide.md介绍 auto 目录下的可选 Ruby 辅助脚本LICENSE.txtMIT 协议条款说明。二、如何创建一个测试文件测试文件本质上是普通的 C 文件。最常见的做法是为每个被测 C 模块对应一个测试文件。测试文件需要包含unity.h以及被测模块的头文件。1. setUp 与 tearDown每个测试的前后钩子测试文件内部需要提供setUp()与tearDown()两个函数setUp()在每个测试执行之前运行可放置任何你想在每个测试前做的准备工作如重置全局计数器、初始化数据结构tearDown()在每个测试执行之后运行用于清理。两个函数都不接受参数、不返回任何值如果不需要两者或其一可以留空。在 unity.c 的默认测试运行器UnityDefaultTestRun中可以看到它们的调用时机先setUp()再执行测试函数Func()随后tearDown()最后UnityConcludeTest()汇总结果。也就是说每个测试都会独立经历一次 setUp → 测试体 → tearDown 的完整生命周期这正是 example_1 中通过 setUp 重置Counter 0x5a5a来保证用例彼此隔离的原理。如果你使用 Ceedling 或测试运行器生成脚本可以完全不写这两个函数。拿不准时直接尝试如果编译器在链接阶段报找不到 setUp 或 tearDown说明你的构建方式需要至少提供空函数在 unity.h 中它们被声明为必须由每个测试可执行文件提供的符号。2. 测试函数的命名约定与约束测试文件的主体是一系列测试函数约定以test_或spec_开头。这不是强制的——不这样命名也能运行但对团队其他开发者而言一眼就能区分哪些是测试函数Unity 自带的自动化脚本以及 Ceedling 默认会按此前缀扫描测试函数参见 auto/generate_test_runner.rb 的扫描逻辑。测试函数同样不接受参数、不返回任何值所有的测试记账计数、成败统计都由 Unity 内部完成。3. main 函数测试的总开关测试文件末尾需要一个main()函数它调用UNITY_BEGIN()开始一次测试会话然后为每一个测试函数调用一次RUN_TEST最后以UNITY_END()收尾。每个测试函数都必须有自己的RUN_TEST调用否则它不会被触发执行。反复手动把每个测试追加进 main 相当繁琐。如果你喜欢在构建流程中使用辅助脚本可以改用 generate_test_runner.rb只要遵循上述命名约定它会自动生成 main 函数和全部RUN_TEST调用此时测试文件里就完全不需要再写 main 了example_1 中由 makefile 调 Ruby 生成 runner 的写法见下文第五节。4. 一个完整的测试文件模板官方入门文档给出了如下完整模板这也是仓库内所有示例测试文件如 TestProductionCode.c遵循的结构#include unity.h #include file_to_test.h void setUp(void) { // set stuff up here } void tearDown(void) { // clean stuff up here } void test_function_should_doBlahAndBlah(void) { //test stuff } void test_function_should_doAlsoDoBlah(void) { //more test stuff } // not needed when using generate_test_runner.rb int main(void) { UNITY_BEGIN(); RUN_TEST(test_function_should_doBlahAndBlah); RUN_TEST(test_function_should_doAlsoDoBlah); return UNITY_END(); }对照仓库中真实的示例文件 TestProductionCode.c可以看到实际项目中的写法setUp里把被测模块的全局变量重置测试函数内部使用TEST_ASSERT_EQUAL(...)、TEST_ASSERT_EQUAL_HEX(...)等断言。值得注意的是Unity 的断言在一个测试函数内的第一条失败即中止——注释中明确写道 Unit tests abort each test function on the first sign of trouble. Then NEXT test function runs as normal.即单个用例失败不会影响后续用例的执行TestProductionCode.c 中对此做了专门演示。三、运行测试函数的两种形态RUN_TEST 的变体当你在自己的 main 中编写测试运行器时有两种执行测试的方式经典形态显式携带行号RUN_TEST(func, linenum)更简单的替代形态行号自动取自调用处RUN_TEST(func)这两个宏都会在执行测试前完成必要的准备工作并在测试后处理清理与结果统计。从源码看unity_internals.h 中RUN_TEST的展开相当精巧在支持变参宏C99 及以上且未自定义RUN_TEST时RUN_TEST(...)会被展开为RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway)再进一步映射为UnityDefaultTestRun(func, #func, line)——这就是第二种形态能够自动补上行号的原因在不支持变参宏的旧编译环境下或定义了 CMOCK 时宏退化为显式传入行号的形态RUN_TEST(func, num)。无论哪种形态最终都调用 unity.c 中的UnityDefaultTestRun(Func, FuncName, FuncLineNum)它记录当前测试名与行号、递增测试计数、通过TEST_PROTECT()保护setUp()与Func()的执行、执行tearDown()最后调用UnityConcludeTest()unity.c对本次测试做 PASS/FAIL/IGNORE 的判定与计数。四、忽略与中止控制测试流程的两个机制1. 忽略测试TEST_IGNORE 与 TEST_IGNORE_MESSAGE当某个测试尚未完成或暂时无效时可以在测试内部调用TEST_IGNORE控制权会立即返回给测试的调用者且不计入失败。这在测试运行器由脚本自动生成所有已存在的测试函数都会被注册进 main时尤其有用——你不必删掉函数只需让它自我忽略。TEST_IGNORE()忽略此测试并立即返回。TEST_IGNORE_MESSAGE(message)忽略此测试并立即返回同时输出一条说明忽略原因的消息。两者在 unity.h 中被定义为UNITY_TEST_IGNORE(__LINE__, ...)最终在 unity.c 附近实现为打印 IGNORE 信息后通过UNITY_IGNORE_AND_BAIL中止当前测试。被忽略的测试会在 UnityConcludeTest 中累计到Unity.TestIgnores最终反映在UNITY_END()输出的统计里。2. 中止测试TEST_PROTECT 与 TEST_ABORT某些测试在错误条件下会陷入死循环或者需要提前跳出测试而不执行剩余部分。Unity 为此提供了一对宏TEST_PROTECT()设置并捕获宏建立一个保护点并处理紧急中止情形。TEST_ABORT()中止测试宏在测试的任何位置调用立即返回到最近一次TEST_PROTECT()调用处。官方示例main() { if (TEST_PROTECT()) { MyTest(); } }如果MyTest()调用了TEST_ABORT程序控制权会立即返回TEST_PROTECT()且其返回值为零。从 unity_internals.h 的实现看这对宏的底层机制是setjmp/longjmp默认配置下TEST_PROTECT()展开为(setjmp(Unity.AbortFrame) 0)TEST_ABORT()展开为longjmp(Unity.AbortFrame, 1)如果定义了UNITY_EXCLUDE_SETJMP_H例如某些不能使用 setjmp 的嵌入式平台则会退化为TEST_PROTECT() 1与TEST_ABORT() return的简化形式。这一对宏也正是UnityDefaultTestRun内部保护 setUp、测试体与 tearDown 的基石。五、如何构建并运行测试文件把单元测试跑起来是接触新测试框架时最大的门槛——尤其对 C/C 这类贴近金属的语言。官方文档建议不要在你的真实硬件上运行单元测试理由如下硬件上限制太多处理能力、内存等硬件上你无法完全控制所有寄存器硬件上的单元测试难度更大单元测试不是系统测试请保持两者分离。取而代之绝大多数开发者选择把测试编译为原生应用如使用 GCC 或 MSVC或运行在模拟器上的应用两者都是不错的选择原生应用的优势是更快、搭建更简单模拟器应用的优势是与目标应用使用同一套编译器。这两种方案的配置选项详见 UnityConfigurationGuide.md。无论哪种方式可能都需要对包含寄存器定义的文件register set做少量调整详见配置指南。构建的本质链接三部分代码无论哪种方式一个测试可执行文件的构建逻辑都是相同的把 unity、测试文件以及被测的 C 文件链接在一起生成一个可执行文件运行它即得到该模块的测试集然后对下一个测试文件重复此过程。这种按测试文件拆分独立可执行文件的灵活性让我们能对系统做更彻底的单元测试同时把所有测试代码挡在最终发布版本之外。以仓库中最简单的示例 example_1 为例其 makefile 展示了最小化的链接方式UNITY_ROOT../.. TARGET_BASE1test1 TARGET1 $(TARGET_BASE1)$(TARGET_EXTENSION) SRC_FILES1$(UNITY_ROOT)/src/unity.c src/ProductionCode.c test/TestProductionCode.c test/test_runners/TestProductionCode_Runner.c INC_DIRS-Isrc -I$(UNITY_ROOT)/src default: $(SRC_FILES1) $(SRC_FILES2) $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $(SRC_FILES1) -o $(TARGET1) $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $(SRC_FILES2) -o $(TARGET2) - ./$(TARGET1) - ./$(TARGET2)其中SRC_FILES1依次包含 Unity 核心实现unity.c、被测源码ProductionCode.c、测试文件TestProductionCode.c、以及由脚本生成的运行器TestProductionCode_Runner.cINC_DIRS同时把被测模块的src目录和 Unity 的src目录加入头文件搜索路径保证#include unity.h与#include ProductionCode.h都能被找到编译器默认使用gccmacOS 上自动切换为clang并启用-Wall -Wextra等一系列告警开关以-stdc89编译makefile这从侧面印证了 Unity 对古老 C 标准的兼容性追求。运行make后会依次生成并执行test1、test2两个测试可执行文件控制台会输出每个测试文件的 PASS/FAIL 汇总。运行器的两种来源手写 main 或脚本生成example_1 展示了自动生成运行器的标准做法test/test_runners/TestProductionCode_Runner.c: test/TestProductionCode.c ruby $(UNITY_ROOT)/auto/generate_test_runner.rb test/TestProductionCode.c test/test_runners/TestProductionCode_Runner.c即用 Ruby 执行 generate_test_runner.rb扫描测试文件中所有test_/spec_前缀函数自动生成包含 main 与全部RUN_TEST调用的运行器 C 文件仓库已附带生成产物 TestProductionCode_Runner.c 可对照查看。当然Ruby 与这些脚本完全是可选的——如果你选择手写 main直接用第二节的模板即可测试文件里就不需要这段 makefile 规则。六、测试输出与结果统计源码视角跑完测试后UNITY_END()负责输出总结。对照 unity.c 的实现可以看到它打印的内容先输出一条分隔线然后依次打印测试总数Unity.NumberOfTests、失败数Unity.TestFailures、忽略数Unity.TestIgnores若失败数为 0 则输出OK否则输出FAIL若定义了UNITY_DIFFERENTIATE_FINAL_FAIL会额外打印ED以便自动化脚本搜索失败标记。UNITY_END()的返回值即失败总数这也是测试文件 main 中return UNITY_END();能直接把失败数作为进程退出码传给 shell例如 CI 系统的原因。UNITY_BEGIN()unity.c则负责初始化记录测试文件名、清零测试数/失败数/忽略数等全局状态。两者在 unity_internals.h 中被宏定义为UnityBegin(__FILE__)与UnityEnd()。七、下一步何时转向配置指南本文的模板与示例足以支撑你搭建第一组测试。当你逐渐需要更多定制例如浮点断言、64 位整数支持、调整输出通道、为特定嵌入式编译器裁剪时请转向 UnityConfigurationGuide.md——unity.h 中注释列出的UNITY_EXCLUDE_FLOAT、UNITY_SUPPORT_64、UNITY_OUTPUT_CHAR、UNITY_LINE_TYPE等编译期配置项都在其中逐一展开说明。测试文件的编写习惯、断言矩阵以及可选的 Ruby 辅助脚本则分别在 UnityAssertionsReference.md 与 UnityHelperScriptsGuide.md 中有完整论述。快速上手清单为每个被测模块建一个test_xxx.c→ 包含unity.h与模块头文件 → 提供或留空setUp/tearDown→ 用test_/spec_前缀写测试函数并放入断言 → 手写 main或交给 generate_test_runner.rb 生成→ 用 gcc 把unity.c 被测源码 测试文件 运行器链接成可执行文件 → 运行并查看UNITY_END()的统计输出。赞分享测试嵌入式【免费下载链接】UnitySimple unit testing for C项目地址https://gitcode.com/gh_mirrors/un/Unity点击查看免费下载相关推荐Linux 内核 KUnit 单元测试框架入门实战从运行现有测试到编写你的第一个测试Linux 内核 KUnit 单元测试框架入门实战从运行现有测试到编写你的第一个测试 本文基于内核仓库官方文档 Getting Started https:/操作系统内核驱动驱动开发虚拟化嵌入式网络存储OpenKore终极指南仙境传说自动化工具从零到精通OpenKore终极指南仙境传说自动化工具从零到精通 还在为仙境传说Ragnarok Online中重复的刷怪、升级、任务而烦恼吗OpenKore这款免游戏开发终极指南如何用Mobaxterm中文版快速管理远程服务器终极指南如何用Mobaxterm中文版快速管理远程服务器 还在为复杂的远程服务器管理而烦恼吗Mobaxterm中文版为你提供了一个一站式解决方案这款基于M上一篇如何使用 mkdocs-material 的 shadow tags 在正式构建中过滤 Draft 等未发布内容下一篇10 分钟给整个音乐文件夹配齐 LRC 歌词163MusicLyrics 保姆级教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表