资讯中心

F´ 单元测试框架实战:TesterBase、GTestBase 与 Tester 的自动生成机制、断言写法与覆盖率分析

📅 2026/9/25 17:35:53
F´ 单元测试框架实战:TesterBase、GTestBase 与 Tester 的自动生成机制、断言写法与覆盖率分析
嵌入式系统编程【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址https://gitcode.com/gh_mirrors/fp/fprime点击查看免费下载F´F Prime是一个用于飞行软件FSW与嵌入式系统开发的组件化框架。在 F´ 中单元测试Unit Testing是组件级质量保障的核心手段它为每个组件生成一套测试骨架TesterBase / GTestBase开发者只需在 Tester 中补充测试逻辑即可完成命令、事件、遥测与自定义输出端口的验证并配合 gcov 完成代码覆盖率分析。本文以 docs/UsersGuide/user/unit-testing.md 为主体结合仓库中 autocoder 的模板实现TestVisitorBase.py、GTestVisitorBase.py与真实测试用例如 Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp深入讲解测试框架的生成原理、断言宏的用法、构建运行命令与覆盖率分析方法。图 1F´ 组件单元测试框架总览。浅蓝色为自动生成的类浅灰色为开发者编写的类XML 组件规范驱动生成组件基类与测试基类二者之间通过反向端口连接。为什么单元测试对飞行软件如此重要测试在飞行软件开发中分为两个阶段单元测试Unit Testing与集成测试Integration Testing。单元测试针对单个单元例如某个 F´ 组件进行验证而集成测试针对组装后的完整系统进行验证。单元测试之所以关键在于它提供了单元级回归测试能力局部错误在早期被捕获系统级问题只在集成阶段才会出现这大大降低了集成难度。F´ 在组件层面提供了完整的单元测试支撑整体框架如图 1 所示。单元测试的目标是覆盖组件级全部需求requirements并在合理的系统状态与路径覆盖下达到接近 100% 的代码覆盖率。因此需求应驱动测试设计并应维护“测试与需求的映射记录”——可以记录在电子表格中也可以写在测试代码的注释里或通过测试中的控制台输出机制体现。在仓库的测试代码中可以看到这种实践例如 Svc/ActiveLogger/test/ut/ActiveLoggerImplTester.cpp 中的REQUIREMENT(AL-001)、REQUIREMENT(AL-002)等调用正是把每个测试与需求编号显式关联的典型用法。测试框架的三个核心类TesterBase、GTestBase 与 TesterF´ 的组件单元测试由三个类协作完成TesterBase被测组件记为C的测试基类提供单元测试的“线束”harness。它是组件C的镜像对C的每个输出端口TesterBase 提供对应的输入端口称为from port对C的每个输入端口TesterBase 提供对应的输出端口称为to port。每个 from port 都有一个历史记录H通过虚输入处理函数把收到的参数存入H。此外TesterBase 还提供发送命令、在端口上发起调用invoke、读取/设置参数和时间等工具方法。GTestBase从 TesterBase 派生集成了 Google Test 框架头文件与 F´ 专用宏。它支持标准断言如ASSERT_EQ(3, x)并提供 F´ 专用宏来检查从端口接收到的遥测telemetry、事件event以及用户自定义数据。GTestBase 被单独拆分为一个类这样在不支持 Google Test 的平台上可以可选地不启用它。Tester从 GTestBase 派生把被测组件作为成员变量包含在内。autocoder 提供一个模板见 test_impl/hpp.tmpl用户在其中以公有方法添加测试也可以在 Tester 的派生类中编写测试。自动生成机制从模板到代码这三个类并非手工编写F´ 的 autocoder 会根据组件的 XML/FPP 定义自动生成TesterBase与GTestBase并生成开发者可编辑的Tester模板。从源码结构看自动生成过程由 Autocoders/Python/src/fprime_ac/generators/visitors/TestVisitorBase.py 与 GTestVisitorBase.py 驱动。其中关键逻辑包括TestVisitorBase.initTest把c.tester_base c.name() TesterBase即按“组件名 TesterBase”命名生成类同时装配命令参数、事件参数、参数端口与遥测类型的转换函数。GTestVisitorBase.initGTest生成c.gtest_base c.name() GTestBase并定义断言所需的调用点参数__callSiteFileName、__callSiteLineNumber、size、index等供ASSERT_CMD_RESPONSE、ASSERT_EVENT、ASSERT_FROM_PORT系列宏使用。模板文件 test/hpp.tmpl 生成TesterBase.hpp其中定义了HistoryT模板类支持push_back、at、size、clear为每个 from port 生成FromPortEntry_Port结构体与HistoryFromPortEntry_Port历史为命令生成sendCmd_Mnemonic与CmdResponse历史为每个事件生成EventEntry_EventName与eventHistory_EventName为每个遥测通道生成TlmEntry_ChannelName与tlmHistory_ChannelName。test_impl/hpp.tmpl 生成Tester.hpp模板其中包含MAX_HISTORY_SIZE 10、TEST_INSTANCE_ID 0、TEST_INSTANCE_QUEUE_DEPTH 10非 passive 组件等默认常量以及connectPorts()、initComponents()帮助函数和toDo()占位测试方法被测组件以ComponentName component;成员形式存在。在 test/cpp.tmpl生成TesterBase.cpp中可以看到运行时行为命令响应对cmdResponseIn调用cmdResponseHistory-push_back(e)每个 from port 通过from_Port_handlerBase调用纯虚from_Port_handler由开发者实现paramSet_Name把值存入m_param_Name成员并在组件通过ParamGet端口请求时由from_PrmGet_static序列化返回。推荐的测试编写风格生成测试类后用户就可以开始发送命令、检查事件与遥测、检查用户自定义输出端口、设置参数与时间、在组件目录构建并运行单元测试最后在组件目录分析代码覆盖率注意覆盖率分析结果请从test/ut目录复核。编写单元测试的标准做法是先写一个完整覆盖需求的测试如果多个测试存在重叠优先重构为函数以避免代码重复更规范的做法是编写只测单一行为的函数。编写测试代码时应把单元测试当作一个编程问题套用与飞行代码一致的风格规范这样能减少重复、提升可读性与可维护性。测试组件时应面向接口测试通过发送命令、在输出端口发送数据来驱动被测组件通过读取内部组件状态来验证其正确性并且只能通过接口修改状态绝不要直接更新被测组件的内部状态。这样会形成更结构化的测试。若确实需要测试组件实现中含复杂算法的某个函数则应针对该函数的接口编写测试。此外应为被测组件建模“接收命令、发送响应”的外部行为并编写测试线束test harness这种模块化测试方式可复用于多个测试。实战命令、事件、遥测与自定义端口的断言写法以下代码模式来自原文档且与仓库真实用例一致可对照 Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp 中的ASSERT_EVENTS_CS_FileNotFound、ASSERT_CMD_RESPONSE等调用。发送命令并断言命令响应// Send command this-sendCOMMAND_NAME( cmdSeq, // Command sequence number arg1, // Argument 1 arg2 // Argument 2 ); this-component.doDispatch(); // Assert command response ASSERT_CMD_RESPONSE_SIZE(1); ASSERT_CMD_RESPONSE( 0, // Index in the history Component::OPCODE_COMMAND_NAME, // Expected command opcode cmdSeq, // Expected command sequence number Fw::CmdResponse::OK // Expected command response );注意两个关键点一是调用sendCmd_Mnemonic后必须调用component.doDispatch()让命令真正进入被测组件并被处理对于 active/queued 组件实际测试中还常见clearAndDispatch()帮助函数见 CmdSequencerTester.cpp二是命令响应通过组件的CmdResponse端口回传到 TesterBase被记入cmdResponseHistory再由ASSERT_CMD_RESPONSE系列宏校验。在 test/cpp.tmpl 中可以看到sendCmd_Mnemonic会把参数序列化进Fw::CmdArgBuffer并依据ComponentBase::OPCODE_MNEMONIC计算操作码后调用Cmd输出端口。检查事件Events// Send command and check response … // Assert total number of events in history ASSERT_EVENTS_SIZE(1); // Assert number of a particular event ASSERT_EVENTS_EventName_SIZE(1); // Assert arguments for a particular event ASSERT_EVENTS_EventName( 0, // Index in history arg1, // Expected value of argument 1 arg2 // Expected value of argument 2 );事件由组件通过Log输出端口发出TesterBase 中的dispatchEvents依据ComponentBase::EVENTID_EVENT反序列化参数后存入eventHistory_EventName。仓库用例 Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp 第 222–224 行展示了典型组合ASSERT_EVENTS_SIZE(1); ASSERT_EVENTS_CS_FileNotFound_SIZE(1); ASSERT_EVENTS_CS_FileNotFound(0, errorFileName);检查遥测Telemetry// Send command and check response … // Assert total number of telemetry entries in history ASSERT_TLM_SIZE(1); // Assert number of entries on a particular channel ASSERT_TLM_ChannelName_SIZE(1); // Assert value for a particular entry ASSERT_TLM_ChannelName( 0, // Index in history value // Expected value );遥测由组件通过Tlm输出端口发出TesterBase 中的dispatchTlm依据ComponentBase::CHANNELID_CHANNEL反序列化后存入tlmHistory_ChannelName。ASSERT_TLM_ChannelName校验时还携带时间标签timeTag与通道值。检查用户自定义输出端口from ports// Send command and check response … // Assert total number of entries on from ports ASSERT_FROM_PORT_HISTORY_SIZE(1); // Assert number of entries on a particular from port ASSERT_from_PortName_SIZE(1); // Assert value for a particular entry ASSERT_from_PortName( 0, // Index in history arg1, // Expected value of argument 1 arg2 // Expected value of argument 2 );from port 的处理函数由开发者以纯虚函数形式实现模板中的from_Port_handlerTesterBase 的pushFromPortEntry_Port会把参数打包成FromPortEntry_Port结构体压入历史。仓库中 Svc/ActiveLogger/test/ut/ActiveLoggerImplTester.cpp 的from_PktSend_handler、from_FatalAnnounce_handler就是开发者实现的典型 from port 处理器。设置参数与时间this-paramSet_ParamName( value, // Parameter value Fw::PARAM_VALID // Parameter status );paramSet_ParamName把参数值存入 TesterBase 的成员变量当组件通过ParamGet端口请求该参数时TesterBase 返回该值参数状态标记为Fw::ParamValid如VALID/INVALID/UNINIT。在 test/cpp.tmpl 的from_PrmGet_static中可以看到完整实现根据参数 ID 从m_param_Name序列化并返回对应的m_param_Name_valid状态。this-setTime(time)time是Fw::Time对象当组件调用TimeGet端口时TesterBase 返回该值对应 test/cpp.tmpl 中from_Time_static直接把m_testTime赋给输出时间。仓库用例 Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp 中的模式Fw::Time testTime(TB_WORKSTATION_TIME, 1, 1); this-setTestTime(testTime);注意测试代码中实际使用的帮助方法名为setTestTime它会写入m_testTime成员原文档中的setTime是同一概念在不同文档语境下的叫法请以生成代码中的实际签名为准。构建、运行与覆盖率分析F´ 的构建系统cmake/target/ut.cmake为组件单元测试提供了专用 target。构建单元测试在组件目录注意不是test/ut目录执行fprime-util generate --ut运行单元测试同样在组件目录执行fprime-util check [parameter flags]单元测试 check 参数如下参数说明--all运行全部单元测试可与coverage组合使用--coverage检查单元测试的代码覆盖率例如运行全部单元测试并检查代码覆盖率fprime-util check --all --coverage从 cmake/target/ut.cmake 的ut_add_module_target可以看到底层机制UT 可执行文件通过run_ac_set(... autocoder/fpp_ut)再次驱动 autocoder 生成测试代码链接gtest_main与依赖库并通过add_test注册为 CTest 用例运行前还会通过_ut_setup_clean_file清理构建目录下的*.gcda文件确保每次覆盖率数据从零开始。选择合适的测试链接库组件若调用第三方库有两种测试写法直接链接被测库在测试中链接真实库不要同时链接 mock/stub 库这样可以证明组件代码与真实库协作正常。链接 mock 或 stub 库更易于诱导特定行为如注入故障便于测试异常路径在某些平台上这可能是唯一选择。覆盖率分析代码覆盖率检查的是哪些行在测试中被执行过至少一次。工具如gcov在编译并运行测试后生成报告。一般来说代码覆盖率应接近80% 的行剩余的行通常是偏离标称的行为需要额外努力——要么从期望行为反向推理来构造输入要么向库行为中注入故障。需要特别注意的是100% 的代码覆盖率并不等于系统状态和代码路径都被测过。覆盖率只反映行的执行情况不反映状态覆盖与路径覆盖。覆盖率分析结果查看方式在组件目录查看汇总输出文件*_gcov.txt在组件目录查看带覆盖率标注的源码文件*.hpp.gcov与*.cpp.gcov每个被覆盖行前会标注执行次数。小结F´ 的单元测试体系把“自动生成测试骨架 开发者填充测试逻辑 gcov 覆盖率闭环”三者结合TesterBase提供端口镜像与历史记录机制GTestBase提供 Google Test 断言与 F´ 专用宏Tester由开发者编写具体用例命令、事件、遥测与自定义端口的断言宏形成了统一的验证范式。配合fprime-util generate --ut、fprime-util check --all --coverage与 gcov 报告开发者可以在组件目录完成从构建、运行到覆盖率分析的完整闭环。深入阅读 Autocoders/Python/src/fprime_ac/generators/templates/test/hpp.tmpl、test/cpp.tmpl 等模板以及 Svc 下各组件test/ut目录中的真实用例可以进一步掌握这套框架的细节。赞分享嵌入式系统编程【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址https://gitcode.com/gh_mirrors/fp/fprime点击查看免费下载相关推荐Sunshine 自托管游戏串流服务器教程把 PC 游戏串到任何设备Sunshine 自托管游戏串流服务器教程把 PC 游戏串到任何设备 Sunshine 是一款免费开源的自托管游戏串流服务器装在你的游戏 PC 上后它会通嵌入式系统编程如何突破Rutracker封锁免费开源工具rutracker-proxy让你轻松访问如何突破Rutracker封锁免费开源工具rutracker proxy让你轻松访问 如果你是一个BitTorrent用户特别是经常使用俄语资源的用户那么Dayz-Cheat-H4ck-A1mbot常见问题从编译错误到游戏崩溃的解决方案Dayz Cheat H4ck A1mbot常见问题从编译错误到游戏崩溃的解决方案 Dayz Cheat H4ck A1mbot是一个基于C开发的DayZ上一篇GoogleTest C 测试框架解析gperftools 仓库中内嵌的测试基石下一篇Agent Zero 共享与安全指南决定分享什么、分享到哪、什么必须保密的完整实战手册创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案