恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

C++ 日志与单元测试:无第三方库的最小实现

  • 首页
  • 资讯中心
  • /
  • C++ 日志与单元测试:无第三方库的最小实现

相关资讯

自定义分配器与内存池:std::pmr 从零到 free list 2026/10/17 6:39:35
PyCharm下Robot Framework自动化测试环境搭建与实战指南 2026/10/17 6:39:35
QC 2.0 快速充电接口控制器D1531 2026/10/17 6:34:35

最新资讯

社区防疫物资申报系统实战:SpringBoot+Vue全栈设计与实现
PAT甲级1001-1159刷题复盘:思路、测试点与代码沉淀
ORB-SLAM2彩色点云补全:从稀疏着色到TSDF稠密重建
Windows 10与Arch Linux双系统安装:LVM分区实战指南
一文讲清,什么是ERP系统?
大模型调用链的数据安全从模型路由到企业级反萃取:TaoToken 统一 Key 通道下的可复制配置与验证

今日推荐

Qwen3.5-122B-A10B-singprobe幻觉检测实战:AUC 0.7806,如何实时揪出大模型一本正经的胡话?
NetSpot 实战:用信道分析与频宽优化解决 Wi-Fi 卡顿
C++ split() 字符串分割的五种实现与性能实测

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

C++ 日志与单元测试:无第三方库的最小实现

发布时间:2026/10/17 6:39:35
C++ 日志与单元测试:无第三方库的最小实现 日志和单元测试是工程里几乎必用的两块基础设施但项目起步阶段引 gtest、spdlog 往往显得重。其实用标准库 几个宏几十行就能写出够用的版本。这篇手写一个最小日志框架用 C20 的std::source_location自动带文件、行号、函数名和一个最小测试框架EXPECT_EQ宏 计数 失败定位并讲清一个关键问题为什么测试框架的宏是省不掉的。1. 引子source_location 让日志自动带位置写日志最烦的是手动传__FILE__、__LINE__。C20 的std::source_location解决了这件事把它作为默认参数就能在调用点自动捕获位置信息调用方什么都不用写。// verify: stdc20#includecstdio#includesource_locationvoidlog(constchar*msg,conststd::source_location locstd::source_location::current()){// file_name() 文件名、line() 行号、function_name() 函数名都在调用点求值printf([%s:%u] %s\n,loc.file_name(),loc.line(),msg);}intmain(){log(hello);log(world);}[prog.cc:~~] hello [prog.cc:~~] world注意输出里的行号是调用log的那一行而不是log函数内部。这就是source_location默认参数的妙处它被隐式传成调用点的位置。所以哪怕再包一层日志仍然指向真正的调用者。官方文档std::source_location ——current()必须在默认参数位置用才能拿到调用点而非函数内部2. 最小日志框架级别 宏封装加上日志级别log level再用宏把「级别 消息」打包调用就变成一行// verify: stdc20#includecstdio#includesource_locationenumclassLevel{Debug,Info,Warn,Error};constchar*level_name(Level lv){switch(lv){caseLevel::Debug:returnDEBUG;caseLevel::Info:returnINFO ;caseLevel::Warn:returnWARN ;caseLevel::Error:returnERROR;}return?????;}voidlog(Level lv,constchar*msg,conststd::source_location locstd::source_location::current()){printf([%s] %s:%u %s\n,level_name(lv),loc.file_name(),loc.line(),msg);}#defineLOG_DEBUG(msg)log(Level::Debug,msg)#defineLOG_INFO(msg)log(Level::Info,msg)#defineLOG_ERROR(msg)log(Level::Error,msg)intmain(){LOG_INFO(程序启动);LOG_DEBUG(变量已初始化);LOG_ERROR(写入失败);}[INFO ] prog.cc:~~ 程序启动 [DEBUG] prog.cc:~~ 变量已初始化 [ERROR] prog.cc:~~ 写入失败日志级别的作用是过滤DEBUG平时淹没在海量信息里发布时只想看WARN以上。级别划分的常见约定级别用途DEBUG开发期诊断发布时默认关掉INFO关键流程节点启动、完成WARN异常但可恢复的情况ERROR出错需要人工关注宏在这里还有一层作用用#ifdef可以在发布构建里把LOG_DEBUG整句编译期抹掉做到零开销这是直接调用函数做不到的。具体做法是开发构建里定义#define LOG_DEBUG(msg) log(Level::Debug, msg)发布构建里换成#define LOG_DEBUG(msg) ((void)0)。这样所有LOG_DEBUG(...)调用点会被整体替换成一条空语句消息字符串的拼接、实参的求值、函数调用全部消失连开销都省了。这是「零开销日志」的经典手法也是宏在日志场景里第二个不可替代的理由它能做编译期裁剪而函数调用做不到。3. 最小测试框架EXPECT_EQ 宏测试框架的核心是一个断言宏比较两个值记录通过/失败失败时打印表达式文本和出错位置。宏之所以必须是因为要拿两样函数拿不到的东西字符串化#a把EXPECT_EQ(add(1,2), 3)里的表达式原样变成字符串打印出来__LINE__失败时定位到具体哪一行。#includecstdiointg_pass0;intg_fail0;#defineEXPECT_EQ(a,b)\do{\autova(a);\autovb(b);\if(vavb){\g_pass;\}else{\g_fail;\printf([FAIL] %s:%d EXPECT_EQ(%s, %s) 左边%d 右边%d\n,\__FILE__,__LINE__,#a,#b,\static_castint(va),static_castint(vb));\}\}while(0)intadd(inta,intb){returnab;}intmain(){EXPECT_EQ(add(1,2),3);EXPECT_EQ(add(1,2),4);// 故意写错触发失败EXPECT_EQ(add(10,20),30);printf(passed%d, failed%d\n,g_pass,g_fail);returng_fail0?0:1;// 有失败就返回非 0供 CI 判断}[FAIL] prog.cc:~~ EXPECT_EQ(add(1, 2), 4) 左边3 右边4 passed2, failed1do { ... } while (0)是宏的经典包裹技巧让EXPECT_EQ(...)后面能安全地跟分号、也能塞进if分支而不会出错。失败信息里#a、#b展开了表达式原文__LINE__给出了行号。这两样正是 gtest 也靠宏实现的原因。官方文档预处理器宏替换含 # 字符串化、FILE与LINE这个最小框架当然远不及 gtest 完整但它们的核心机制一模一样——都是宏 字符串化 行号定位。差别在功能丰富度能力这里手写gtest断言种类只有EXPECT_EQ几十种NE/TRUE/FLOAT_EQ/THROW…失败定位__FILE__/__LINE__同也靠宏测试发现手动写在main里自动注册 RUN_ALL_TESTS()fixture初始化/清理无有SetUp/TearDown依赖纯标准库第三方库看到这层你就明白 gtest 的TEST、EXPECT_*宏不是故弄玄虚而是「要拿字符串化表达式 行号」这个刚需逼出来的。手写版的意义在于几十行就够你在不引库的项目里先跑起测试等规模大了再无缝迁移到 gtest——断言写法的心智模型是一样的。4. 完整示例日志 测试套件合体把日志和测试框架拼进一个文件就是一个能直接跑的最小测试套件日志用source_location自动带函数名测试用EXPECT_EQ自动计数和定位// verify: stdc20#includecstdio#includesource_location// 日志 enumclassLevel{Debug,Info,Warn,Error};constchar*level_name(Level lv){switch(lv){caseLevel::Debug:returnDEBUG;caseLevel::Info:returnINFO ;caseLevel::Warn:returnWARN ;caseLevel::Error:returnERROR;}return?????;}voidlog(Level lv,constchar*msg,conststd::source_location locstd::source_location::current()){// function_name() 已经带了签名如 int main()后面不再补括号printf([%s] %s %s:%u %s\n,level_name(lv),loc.function_name(),loc.file_name(),loc.line(),msg);}#defineLOG_INFO(msg)log(Level::Info,msg)#defineLOG_ERROR(msg)log(Level::Error,msg)// 测试框架 intg_pass0,g_fail0;#defineEXPECT_EQ(a,b)\do{\autova(a);autovb(b);\if(vavb){g_pass;}\else{g_fail;\printf([FAIL] %s:%d EXPECT_EQ(%s, %s) %d ! %d\n,\__FILE__,__LINE__,#a,#b,\static_castint(va),static_castint(vb));}\}while(0)// 被测代码 intmul(inta,intb){returna*b;}intmain(){LOG_INFO(开始测试 mul);EXPECT_EQ(mul(6,7),42);EXPECT_EQ(mul(6,7),43);// 失败用例EXPECT_EQ(mul(0,100),0);LOG_ERROR(发现 1 个失败用例);printf(passed%d failed%d\n,g_pass,g_fail);returng_fail0?0:1;}[INFO ] ~~ prog.cc:~~ 开始测试 mul [FAIL] prog.cc:~~ EXPECT_EQ(mul(6, 7), 43) 42 ! 43 [ERROR] ~~ prog.cc:~~ 发现 1 个失败用例 passed2 failed1一次失败的完整执行流是这样的EXPECT_EQ(mul(6, 7), 43) │ ▼ 宏展开编译期 do { auto va (mul(6, 7)); // 42 auto vb (43); // 43 if (va vb) g_pass; else { g_fail; printf(... %s:%d ..., __FILE__, __LINE__); } } while (0) │ ▼ 运行va42, vb43不相等 [FAIL] prog.cc:42 EXPECT_EQ(mul(6, 7), 43) 42 ! 43 │ ▼ 汇总 passed2 failed1 → return 1CI 据此判定失败官方文档C Core Guidelines ES.30 —— 宏的谨慎使用原则测试断言是「字符串化」这类极少数正当场景之一5. 延伸阅读std::source_location — cppreferencecurrent()的调用点语义、function_name()等成员预处理器宏替换# 与 ##字符串化与拼接宏的能力边界FILE/LINE— cppreference预定义宏的来源C Core Guidelines ES 系列宏的谨慎使用原则本知识库内的相关篇目《C 跨平台工程特性检测与 feature-test 宏实战》 —— 一份代码要在 Windows/Linux/macOS 上都能编关键是不写死平台和标准。《C ABI 与插件系统dlopen 导出稳定接口》 —— 为什么插件系统都要用 C ABI这篇从名字修饰讲起《C 序列化与版本兼容没有反射手写字段也能持久化》 —— C 没有内置反射序列化只能靠显式声明字段。6. 一句话总结不引第三方库日志和测试框架也能写得小而够用日志靠 C20 的std::source_location默认参数自动拿调用点文件、行号、函数名测试框架靠EXPECT_EQ宏拿函数给不了的两样东西#字符串化的表达式文本和__LINE__的失败定位。理解了这两点也就理解了 gtest、spdlog 背后在做什么。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号