免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

自定义 Catch2 的 main 入口:从接管命令行参数到扩展自己的 CLI 选项

自定义 Catch2 的 main 入口:从接管命令行参数到扩展自己的 CLI 选项 自定义 Catch2 的 main 入口从接管命令行参数到扩展自己的 CLI 选项【免费下载链接】Catch2A modern, C-native, test framework for unit-tests, TDD and BDD - using C14, C17 and later (C11 support is in v2.x branch, and C03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2本篇文章聚焦 Catch2 测试框架中由用户自己编写main函数这一实战场景如何链接Catch2::Catch2库并绕开框架自带的入口从而在测试运行前后注入初始化与清理代码、编程式修改运行配置、甚至通过 Clara 解析器往 Catch2 的命令行里添加自定义选项。读完本文你将掌握 Catch2 中Catch::Session的完整用法、四种自写main的典型配方以及版本宏的检测技巧可以直接在你的测试工程中落地。Catch2 最省事的使用方式是链接Catch2::Catch2WithMain目标让框架自带的main全权接管命令行参数。但当你需要自己的主函数时Catch2 同样提供了成熟的支持只链接静态库Catch2::Catch2不含 main 部分然后手动调用测试运行器。本文主体内容基于官方文档 docs/own-main.md并结合仓库源码与示例做纵深展开。为什么要自写 main以及如何切换链接目标默认情况下Catch2 的main实现位于 src/catch2/internal/catch_main.cpp。它的核心逻辑极简int main (int argc, char * argv[]) { // 让链接器不要丢弃泄漏检测器的全局变量 (void)Catch::leakDetector; return Catch::Session().run( argc, argv ); }也就是说框架自带入口本质上就是构造一个Catch::Session并调用run(argc, argv)。因此自写main完全等价于手动复刻这一过程只是把控制权交回给你。在 CMake 层面切换方式在 docs/cmake-integration.md 中有明确说明Catch2 导出两个命名空间目标——Catch2::Catch2WithMain链接两个静态库框架本体 main不需要自定义 main 时永远用它Catch2::Catch2仅框架本体需要自定义 main 时只用它。find_package(Catch2 3 REQUIRED) # 这些测试可以用 Catch2 自带的 main add_executable(tests test.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain) # 这些测试需要自己的 main add_executable(custom-main-tests test.cpp test-main.cpp) target_link_libraries(custom-main-tests PRIVATE Catch2::Catch2)如果你的测试文件与 main 写在不同文件中只需把两个.cpp一起加入同一个可执行目标即可。仓库示例 examples/CMakeLists.txt 中所有示例均链接Catch2WithMain而其中唯一演示自定义 main 的 examples/232-Cfg-CustomMain.cpp 也在同一个目标里编译运行——你可以把它当作最小可运行模板。另外使用单一头文件版amalgamated时还可以通过定义CATCH_AMALGAMATED_CUSTOM_MAIN来移除 extras/catch_amalgamated.cpp 中内置的 main见 src/catch2/internal/catch_main.cpp 处的#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN)分支。配方一让 Catch2 全权接管参数只做前后置处理如果你只是需要在测试运行前后执行一些代码并不想干预命令行解析那么这是最简洁的写法构造Catch::Session后一次性调用run(argc, argv)它内部会先完成命令行解析再执行测试并返回退出码。#include catch2/catch_session.hpp int main( int argc, char* argv[] ) { // 你的初始化代码 ... int result Catch::Session().run( argc, argv ); // 你的清理代码... return result; }关于此处的关键实现细节可以从源码确认Session::run(argc, argv)模板版本src/catch2/catch_session.hpp会先调用applyCommandLine(argc, argv)仅在解析成功返回 0后才调用无参的run()无参run()src/catch2/catch_session.cpp负责处理--wait-for-keypress的前置等待逻辑随后委托给runInternal()完成实际测试执行。注意如果只是想在测试开始前跑一些 setup官方文档建议优先考虑事件监听器event listeners 而不是自写 main因为监听器在多个测试用例间按事件粒度更灵活。配方二修正AmendingCatch2 的配置如果希望 Catch2 照常处理命令行参数但同时想在程序里对最终生效的运行配置做调整可以用两阶段写法先applyCommandLine再通过configData()修改配置最后手动调用run()。int main( int argc, char* argv[] ) { Catch::Session session; // 全局必须只有一个实例 // 在 applyCommandLine 之前写 session.configData() 是在设置默认值 // 这是设置默认值的推荐方式 int returnCode session.applyCommandLine( argc, argv ); if( returnCode ! 0 ) // 非 0 表示命令行解析出错 return returnCode; // 在这里写 session.configData() 或 session.Config() // 会覆盖命令行传入的参数 —— 只有明确知道需要时才这么做 returnCode session.run(); // returnCode 编码了出错类型具体每个返回码的含义 // 请参考 catch_session.hpp 中的整数常量 return numFailed; }默认值 vs 覆盖值configData()与Config()的取舍Session暴露了两个配置入口src/catch2/catch_session.hpp方法时机语义session.configData()applyCommandLine之前设置默认值会被命令行参数覆盖官方推荐session.configData()/session.Config()applyCommandLine之后覆盖命令行参数需要明确知道后果才用其中ConfigData是全部运行配置的载体定义于 src/catch2/catch_config.hpp涵盖了测试筛选testsOrTags、pathFilters、报告器reporterSpecifications、随机种子rngSeed、分片shardCount/shardIndex、Benchmark 采样参数benchmarkSamples、benchmarkResamples、benchmarkWarmupTime等几十个字段。一个常见的实用例子是命令行没有传--seed时程序化设定固定种子以保证 CI 上结果可复现Catch::Session session; if ( session.configData().rngSeed 0 ) { // 命令行未指定种子随机 session.configData().rngSeed 12345; } int rc session.applyCommandLine( argc, argv ); if ( rc ! 0 ) { return rc; } return session.run();如果你想要对配置的完全控制官方建议干脆不调用applyCommandLine只通过useConfigData(ConfigData const)src/catch2/catch_session.hpp注入你自己拼好的ConfigData。退出码的含义run()的返回值编码了出错的类型。这些常量定义在 src/catch2/catch_session.hpp常量值含义UnspecifiedErrorExitCode1未指定的错误如启动期异常NoTestsRunExitCode2没有运行任何测试UnmatchedTestSpecExitCode3测试规格没有匹配到任何测试AllTestsSkippedExitCode4所有测试均被跳过InvalidTestSpecExitCode5测试规格非法TestFailureExitCode42有测试失败注意返回的是退出码而非失败测试数量文档中return numFailed;只是示意——实际应直接返回session.run()的返回值。配方三用 Clara 组合解析器添加自定义命令行选项Catch2 的命令行解析器是内置的 Clara仓库中即 third_party/clara.hpp并打包进 src/catch2/internal/catch_clara.hpp。它采用组合式composable设计你可以取出 Catch2 的默认解析器session.cli()用operator|拼接一个新的Opt选项再通过session.cli(cli)交还给框架。int main( int argc, char* argv[] ) { Catch::Session session; // 全局必须只有一个实例 int height 0; // 希望能在命令行设置的用户变量 // 在 Catch2 解析器之上构建新解析器 using namespace Catch::Clara; auto cli session.cli() // 取出 Catch2 的命令行解析器 | Opt( height, height ) // 把变量绑定到新选项并给出提示字符串 [-g][--height] // 该选项响应的名称可多个 (how high?); // 帮助输出中显示的描述文本 // 把组合好的解析器交还给 Catch2 session.cli( cli ); // 让 Catch2经由 Clara解析命令行 int returnCode session.applyCommandLine( argc, argv ); if( returnCode ! 0 ) // 非 0 表示命令行解析出错 return returnCode; // 如果命令行里设置了该选项此时 height 已被赋值 if( height 0 ) std::cout height: height std::endl; return session.run(); }这段代码与仓库中的真实示例 examples/232-Cfg-CustomMain.cpp 结构一致该示例只注册了--height一个短名。组合语法背后的源码原理Opt的本质定义于 third_party/clara.hppOpt(T ref, std::string const hint)把选项绑定到一个变量引用值型选项hint会出现在帮助文本中如--height heightOpt(bool ref)无 hint 重载绑定布尔标志型选项--flag型不带参数operator[](std::string const optName)逐个登记选项名支持多个名称如[-g][--height]它们会被归一化后做匹配normaliseOpt解析时Opt::parse若遇到匹配的 token标志型直接setFlag(true)值型则消费下一个 token 作为参数值setValue(...)若后续没有参数会返回运行时错误Expected argument following ...operator|把各个Opt/Arg/ExeName组合成Parser因此你可以继续追加更多自定义选项每个选项用|连接。由此可以推断Catch2 默认 CLI 本身也正是用同样的 Clara 构件逐条定义如--reporter、--rng-seed、--shard-count等自写 main 的选项扩展走的是与框架内部完全一致的机制因此你的自定义选项会与内置选项一样支持帮助输出、错误提示等行为。Clara 解析器其余构件Clara命名空间中还提供其他构件均在 third_party/clara.hpp 中Arg位置参数third_party/clara.hpp与Opt的--name value不同它直接消费裸 tokenExeName可执行文件名占位third_party/clara.hpp一般用于帮助文本首行Help-h/--help帮助选项。大多数自定义 main 场景只需Opt即可更完整的 Clara API 细节可参考该头文件与 Catch2 内置的命令行构建代码 src/catch2/internal/catch_commandline.cpp。配方四版本检测宏Catch2 提供了三个宏来暴露头文件版本号CATCH_VERSION_MAJORCATCH_VERSION_MINORCATCH_VERSION_PATCH它们各自展开为一个整数对应版本号的相应部分。以 v3.15.2 为例三个宏分别展开为3、15、2。其定义位于 src/catch2/catch_version_macros.hpp#define CATCH_VERSION_MAJOR 3 #define CATCH_VERSION_MINOR 15 #define CATCH_VERSION_PATCH 2典型用途是在自定义 main 中做条件编译或运行时版本判断例如#if CATCH_VERSION_MAJOR 3 // 仅 Catch2 v3 才有的行为 #endif注意这三个宏反映的是头文件/库的版本与仓库根目录 CMakeLists.txt 中维护的版本号一致与运行时--version输出的信息用途不同——前者用于编译期检测 API 可用性后者用于诊断当前二进制。综合示例初始化 默认配置 自定义选项将上述配方组合起来一个覆盖前后置处理、默认配置注入、自定义选项、退出码透传的完整自定义 main 形如#include catch2/catch_session.hpp #include catch2/catch_config.hpp #include iostream int main( int argc, char* argv[] ) { Catch::Session session; // 全局唯一实例 // 1) 设置默认值命令行可以覆盖 if ( session.configData().rngSeed 0 ) { session.configData().rngSeed 20240101; } // 2) 注册自定义选项 int height 0; using namespace Catch::Clara; auto cli session.cli() | Opt( height, height )[-g]--height; session.cli( cli ); // 3) 解析命令行 int rc session.applyCommandLine( argc, argv ); if ( rc ! 0 ) { return rc; } // 4) 测试前初始化 std::cout height: height \n; // 5) 运行并透传退出码 return session.run(); }总结与 FAQ场景链接目标调用的 Session 接口用自带 mainCatch2::Catch2WithMain无需自己写 main仅前后置处理Catch2::Catch2run(argc, argv)解析后覆盖配置Catch2::Catch2applyCommandLineconfigData()run()完全掌控配置Catch2::Catch2useConfigData(...)不调用applyCommandLine添加自定义 CLI 选项Catch2::Catch2cli()/cli(newParser)applyCommandLinerun()Catch::Session必须保证全局只有一个实例这一点在官方文档与 src/catch2/catch_session.hpp 的类声明中均被强调该类继承Detail::NonCopyable不可拷贝/移动applyCommandLine返回非 0 即表示命令行解析错误此时应直接返回该值而非继续运行返回码请使用 src/catch2/catch_session.hpp 中的命名常量不要硬编码魔法数字Windows 平台若定义了CATCH_CONFIG_WCHAR与_WIN32/UNICODESession还额外提供wchar_t版本的applyCommandLine重载src/catch2/catch_session.hpp便于宽字符命令行场景若只是想测试前做 setup优先评估事件监听器方案它能按事件粒度控制而无需接管main。通过本文的四种配方你可以在完全保留 Catch2 全部命令行能力的前提下为测试运行器注入自己的启动/清理逻辑、程序化配置乃至全新的 CLI 选项把 Catch2 无缝嵌入到你的构建与 CI 体系中。【免费下载链接】Catch2A modern, C-native, test framework for unit-tests, TDD and BDD - using C14, C17 and later (C11 support is in v2.x branch, and C03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表