拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Marlin 固件单元测试完全指南:从配置矩阵到 Makefile 一键执行

Marlin 固件单元测试完全指南从配置矩阵到 Makefile 一键执行【免费下载链接】MarlinMarlin is a firmware for RepRap 3D printers optimized for both 8 and 32 bit microcontrollers. Marlin supports all common platforms. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.项目地址: https://gitcode.com/GitHub_Trending/ma/MarlinMarlin 固件在持续演进中引入了两套自动化测试体系其中**单元测试Unit Tests**专门用于在开发机上验证纯逻辑代码的正确性为重构与功能迭代提供回归护栏。本篇以仓库中 Marlin/tests/README.md 为骨架结合test目录、Marlin/tests测试源码、顶层 Makefile 与 ini/native.ini 等实际文件完整讲解 Marlin 单元测试的架构、配置矩阵、测试编写方式与运行方法。读完本文你将能够在本机或 Docker 中运行全部单元测试理解一个配置一个测试二进制的设计动机并学会借助MARLIN_TEST宏为 Marlin 的宏、类型与 G-code 解析逻辑编写自己的回归测试。Marlin 测试体系总览Build Tests 与 Unit Tests 的分工根据仓库根目录下的 test/README.mdUnit Tests 的完整说明文档Marlin 包含两类自动化测试构建测试Build Tests位于buildroot/tests目录为数十种真实板卡与配置组合执行完整编译用于捕获语法错误与代码构建错误。单元测试Unit Tests位于test目录用于捕获实现逻辑错误即运行时不涉及硬件的功能行为是否正确。这两者的分工可以从目录结构直接看出buildroot/tests下按板卡/机型如mega2560、STM32F103RC_btt、linux_native等存放编译测试用的 INI 配置而test目录则专注于在 Linux 本机以BOARD_SIMULATED方式运行逻辑测试。Marlin/tests/README.md明确说明Marlin/tests目录下的测试文件由root/test目录构建出的单元测试二进制执行。单元测试的价值在于它可以在任意开发者本地机器以及通用的 CI 机器上运行无需连接真实打印板。相比之下在目标控制器上直接跑单元测试既不现实也未实现——那需要专门的测试台架远不如在开发/构建机上测试来得通用。从源码结构看Marlin 目前采用的是 PlatformIO 单元测试框架 Unity 断言库的组合这从 ini/native.ini 中的lib_deps throwtheswitch/Unity^2.6.0可以得到直接印证。单元测试能做什么、不能做什么测试边界单元测试可以验证单个函数或整个特性的逻辑前提是该逻辑不依赖真实硬件。例如可以测试 G-code 命令是否正确解析、是否产生预期的状态变更但无法直接测试G-code 是否触发了限位开关或断料传感器——除非额外增加一层引脚模拟。回归护栏的意义单元测试最典型的价值在编写测试的初期就能发现大量问题此后则持续为代码库尤其是核心类与核心类型提供防回归能力对重构尤其有用。原文档 FAQ 中的关键结论值得记住写单元测试是否很费工夫是的对未按可测试性设计的存量代码尤其如此。需要结合实际判断在何处、以什么粒度实施测试。投入虽大但在防止回归、故障定位上的回报显著。会不会让重构更难既有会直接引用被测代码的测试需要同步修改也有不会——测试反过来给重构提供了机制仍然按预期工作的保障。如何调试失败的单元测试没有现成捷径。理论上可以通过 PlatformIO 交互式调试但配置起来需要一定创造性。好在单元测试通常极小即使不交互调试也能很快定位到问题根源附近。单元测试架构一个配置对应一个测试二进制Marlin 的单元测试架构建立在两个关键事实上Marlin 只编译配置所要求的代码条件编译贯穿整个源码如#if ENABLED(FEATURE)。因此任何配置变化都必须重新生成一个独立的测试二进制。于是架构呈现出清晰的四步流程源自 test/README.mdtest目录存放一组 INI 配置文件config.ini每个文件代表一套用于单元测试的配置选项组合所有适用的单元测试会为其中每一个配置各跑一遍。Marlin/tests目录存放全部单元测试的 C 源码。测试通过 Marlin 宏ENABLED(feature)、TERN(FEATURE, A, B)等决定注册哪些测试、改变测试行为。linux_native_test这个 PlatformIO 环境指定了一个脚本负责收集Marlin/tests下全部测试并把它们加入 PlatformIO 的测试目标列表。测试最终由顶层 Makefile 的unit-test-all-local或unit-test-all-local-docker命令构建并执行。深入测试基础设施test目录与 Unity 主程序单元测试注册机制MARLIN_TEST宏test/unit_tests.h 定义了测试注册的核心一个MarlinTest类和一个MARLIN_TEST(SUITE, NAME)宏。宏会生成一个继承自MarlinTest的类在静态初始化期把测试实例注册进全局测试链表测试名格式为SUITE___NAME三个下划线分隔。用法形如MARLIN_TEST(test_suite_name, test_name) { // Test body }宏内部自动完成类定义、实例化与TestBody静态函数的声明/定义__FILE__与__LINE__会被记录供 Unity 报告失败位置。主程序unit_tests.cpptest/unit_tests.cpp 为所有单元测试二进制提供唯一的main()它遍历全局注册链表中的每个MarlinTest并通过 Unity 执行结构如下int main(int argc, char **argv) { UNITY_BEGIN(); run_all_marlin_tests(); UNITY_END(); return 0; }MarlinTest::run()会把file源文件名和line交给UnityDefaultTestRun从而让断言失败信息精确指向源码位置。同时unit_tests.h无条件包含src/inc/MarlinConfig.h确保所有测试都能看到当前配置宏——这正是同一份测试源码在不同配置下行为不同的前提。测试源码文件Marlin/tests下的测试按主题组织对应MARLIN_TEST的 suite 参数文件覆盖主题关键验证点Marlin/tests/core/test_macros.cpp核心宏位操作、几何/数值宏、配置宏、数组与参数展开宏Marlin/tests/core/test_types.cpp核心类型XYval/XYZval/XYZEval、FlagsN、字符串与毫秒宏Marlin/tests/gcode/test_gcode.cppG-code 解析命令号上限、溢出拒绝、参数seen检测Marlin/tests/feature/test_runout.cpp断料传感器poll_runout_states默认状态位**宏测试test_macros.cpp**是很好的入门样本它直接验证src/core/macros.h中的行为例如位操作宏MARLIN_TEST(macros_bitwise_8, SBI) { uint8_t n; n 0x00; SBI(n, 0); TEST_ASSERT_EQUAL(0x01, n); // LSB n 0x00; SBI(n, 7); TEST_ASSERT_EQUAL(0x80, n); // MSB n 0x00; SBI(n, 3); TEST_ASSERT_EQUAL(0x08, n); // 中间位 }配置宏测试如ENABLED/DISABLED/ANY/ALL/NONE/COUNT_ENABLED/MANY/TERN系列、OPTITEM/OPTARG/OPTCODE验证了 Marlin 条件编译体系本身的正确性这是整个固件可配置性的根基。**类型测试test_types.cpp**覆盖src/core/types.h中的XYval/XYZval/XYZEvalbool 转换、reset/set、magnitude、small/large、四则运算、ABS/ROUNDL/reciprocal、FlagsN1/8/16/32/64/302 位、AxisFlags/AxisBits以及 src/core/mstring.h 的MString/SString与 src/core/millis_t.h 的PENDING/ELAPSED注意测试注释中提及 32 位毫秒计数的环绕语义。其中small/large对负数语义的测试甚至保留了BUG?注释体现了测试即文档的特点。**G-code 解析测试test_gcode.cpp**直接驱动src/gcode/parser.hMARLIN_TEST(gcode, parse_g_code_number_limit) { char max_command[] G65535; parser.parse(max_command); TEST_ASSERT_TRUE(parser.is_command(G, UINT16_MAX)); // 上限合法 char overflow_command[] G65536; parser.parse(overflow_command); TEST_ASSERT_EQUAL(?, parser.command_letter); // 溢出被拒绝 }parse_g1_xz与parse_g1_nxz则验证带 N 行号前缀时parser.seen(X)/seen(Z)等参数检测仍正确工作。**断料测试test_runout.cpp**演示了宏驱动的条件注册——整个测试体被#if ENABLED(FILAMENT_RUNOUT_SENSOR)包裹只有启用该特性时才编译测试断言FilamentSensorBase的poll_runout_states()默认返回每个挤出器一位的位掩码~(~0U NUM_RUNOUT_SENSORS)。配置矩阵test目录下的 INI 文件test目录共三个配置文件构成单元测试的配置矩阵。文件命名带数字前缀001-、002-、003-由收集脚本剥去前缀后作为测试目标名。001-default.ini —— 最小基线配置test/001-default.ini 刻意保持除主板外为空仅声明使用基础配置并强制模拟主板[config:base] ini_use_config base # Unit tests must use BOARD_SIMULATED to run natively in Linux motherboard BOARD_SIMULATED关键约定单元测试必须使用BOARD_SIMULATED才能在 Linux 上原生运行。文件注释还强调如果测试需要改动配置应当新增独立配置文件而不是修改本文件。002-extruders_1_runout.ini —— 单挤出器 断料test/002-extruders_1_runout.ini 针对单挤出器断料场景启用了完整功能链[config:base] ini_use_config base motherboard BOARD_SIMULATED filament_runout_sensor on fil_runout_pin 4 # dummy advanced_pause_feature on emergency_parser on nozzle_park_feature on # Option to support testing parsing with parentheses comments enabled paren_comments on注意fil_runout_pin 4标注为dummy——模拟主板上该引脚仅用于通过健全性检查Sanity Check并非真实硬件引脚。额外启用paren_comments是为了覆盖带括号注释的 G-code 解析路径对应test_gcode.cpp中process_parsed_command等用例的运行环境。003-extruders_3_runout.ini —— 三挤出器 断料test/003-extruders_3_runout.ini 将矩阵扩展到三个挤出器、三个断料传感器[config:base] ini_use_config base motherboard BOARD_SIMULATED extruders 3 temp_sensor_1 1 temp_sensor_2 1 num_runout_sensors 3 filament_runout_sensor on fil_runout_pin 4 # dummy fil_runout2_pin 4 # dummy fil_runout3_pin 4 # dummy filament_runout_script M600 %%c advanced_pause_feature on emergency_parser on nozzle_park_feature on其中filament_runout_script M600 %%c演示了 INI 转义——%%最终解析为字面量%即断料触发时执行的 M600 脚本带挤出器占位符。NUM_RUNOUT_SENSORS 3会直接影响test_runout.cpp中断料状态位掩码的期望值这正是同一测试在不同配置下验证不同行为的实例。这组配置与 buildroot/tests/linux_native/config-01.ini 同属 Linux 原生测试阵营但前者服务于单元测试后者服务于构建测试二者在 buildroot/tests 与 test 两个目录中分离存放。运行单元测试Makefile 命令Marlin/tests/README.md明确指向顶层 Makefile。相关的核心目标如下见 Makefile命令作用底层实现make unit-test-all-local本机运行全部单元测试配置platformio run -t test-marlin -e linux_native_testmake unit-test-all-local-dockerDocker 中运行全部单元测试容器内执行make unit-test-all-localmake unit-test-single-local只跑单个配置的单元测试platformio run -t marlin_$(UNIT_TEST_CONFIG) -e linux_native_testmake unit-test-single-local-dockerDocker 中跑单个配置容器内执行unit-test-single-local常用变量UNIT_TEST_CONFIG指定要运行的配置名不含数字前缀默认值为default即对应001-default.ini。例如要跑断料配置可执行make unit-test-single-local UNIT_TEST_CONFIGextruders_1_runoutVERBOSE_PLATFORMIO置任意值可输出完整的 PlatformIO 日志。GIT_RESET_HARD供 CI 使用会重置本地改动Makefile 中明确警告这会撤销你所有的改动本地调试慎用。Docker 方式若本机缺少 PlatformIO 环境可以先用make setup-local-docker构建marlin-dev镜像基于 docker/Dockerfile带用户名/UID/GID 参数保证容器内文件权限一致然后执行make unit-test-all-local-dockerMakefile 会在镜像不存在时自动先构建unit-test-single-local-docker、unit-test-all-local-docker均包含镜像检查逻辑。PlatformIO 层面linux_native_test环境与测试收集脚本环境定义ini/native.ini 中[env:linux_native_test]继承自[env:linux_native]其关键点平台为native纯本机编译无 Arduino 框架编译宏包含-D__PLAT_LINUX__、-stdgnu17源码过滤为src/HAL/LINUXLinux 硬件抽象层通过extra_scripts的post脚本挂载buildroot/share/PlatformIO/scripts/collect-code-tests.pybuild_src_filter追加tests把test目录纳入构建依赖throwtheswitch/Unity^2.6.0提供断言框架并开启-Werror把警告视为错误。测试收集脚本buildroot/share/PlatformIO/scripts/collect-code-tests.py 是整个配置矩阵机制的实现核心扫描./test/*.ini用正则^\d-|\.ini$剥离数字前缀得到配置名如default、extruders_1_runout、extruders_3_runout为每个配置注册自定义 PIO 目标marlin_name其动作序列为restore_configs → cp -f ini ./Marlin/config.ini → 运行 configuration.py 生成配置 → platformio test -e linux_native_test -f name → restore_configs即把对应 INI 复制为Marlin/config.ini、重新生成配置、再对指定的测试过滤器运行platformio test跑完恢复原配置额外注册聚合目标test-marlin依次执行所有marlin_name即 Makefile 中unit-test-all-local所触发的目标。因此make unit-test-all-local的实际效果等价于对test目录下每一个 INI 配置分别构建一个独立的linux_native_test二进制并执行其中所有注册的测试——这正对应Marlin/tests/README.md所述收集所有测试文件并编译进多个 PlatformIO 测试二进制。常见问题与调试建议如何判断某个测试是否适用于当前配置观察测试源码是否被宏包裹。test_runout.cpp用#if ENABLED(FILAMENT_RUNOUT_SENSOR)说明它只在启用断料传感器的配置如extruders_1_runout/extruders_3_runout中注册而test_macros.cpp、test_types.cpp中的大部分用例无硬件依赖会在所有配置下执行。新增一个测试配置矩阵按test目录的命名规范新建NNN-name.ini复用[config:base]并设置motherboard BOARD_SIMULATED收集脚本会自动发现并注册对应目标——脚本本身不可修改但新增配置文件即可生效仓库为只读本说明仅供理解机制。失败定位由于每个测试都记录__FILE__/__LINE__Unity 会直接报告失败的源文件与行号MARLIN_TEST命名中的 suite 前缀如macros_bitwise_8、types、gcode、runout也便于按主题筛选。环境差异tests-all-local构建测试在 macOS 上会跳过linux_native目标见 Makefile 中uname Darwin的判断而单元测试目标不在此限制内此外 Makefile 要求 Python 3缺失或版本不符会在解析阶段直接报错。结语从Marlin/tests/README.md这短短五行的入口出发可以串起 Marlin 单元测试的完整链路test目录的 INI 配置矩阵定义测什么配置Marlin/tests的 C 源码定义测什么逻辑collect-code-tests.py 与 linux_native_test 环境负责如何构建多个二进制而顶层 Makefile 则是统一入口。理解这条链路后无论是复现 CI 失败、为某个新功能补回归测试还是扩展配置覆盖都有据可循、有命令可用。建议进一步阅读 test/README.md 获取测试哲学的完整阐述并结合 Marlin/tests/core/test_macros.cpp 与 Marlin/tests/gcode/test_gcode.cpp 等真实用例学习断言写法。【免费下载链接】MarlinMarlin is a firmware for RepRap 3D printers optimized for both 8 and 32 bit microcontrollers. Marlin supports all common platforms. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.项目地址: https://gitcode.com/GitHub_Trending/ma/Marlin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门