使用lcov统计gtest分支覆盖率时std::map相关分支未覆盖求助
解决std::map分支覆盖率未覆盖问题的方案及异常触发方法
一、先明确未覆盖分支的根源
g++ 11.4的libstdc++实现中,std::map的emplace/insert/operator[]等函数内部包含大量异常安全、内存分配失败处理的分支。这些分支在常规测试场景下很难触发——要么是内存分配失败在现代系统中几乎不会发生,要么是只有当自定义类型的构造/赋值操作抛出异常时,才会走到标准库的回滚逻辑。
二、触发map相关函数异常分支的具体操作
你之前靠自定义类构造抛异常解决了vector的问题,但map的逻辑更特殊,不同函数的异常触发点不一样:
1. 触发insert/emplace的异常分支
insert和emplace只有在插入新键时才会构造新元素,此时如果元素构造抛出异常,会触发标准库内部的回滚分支。如果键已存在,不会构造新元素,自然触发不了异常。
示例代码:
class ThrowOnConstruct { public: ThrowOnConstruct(bool throwFlag) { if (throwFlag) { throw std::runtime_error("Construct failed"); } } // map要求元素可比较,必须提供<运算符 bool operator<(const ThrowOnConstruct&) const { return false; } }; TEST(MapCoverage, EmplaceThrow) { std::map<int, ThrowOnConstruct> testMap; // 插入新键,触发构造函数抛异常 EXPECT_THROW(testMap.emplace(1, true), std::runtime_error); // 验证map状态未被破坏 EXPECT_TRUE(testMap.empty()); }
2. 触发operator[]的异常分支
operator[]的逻辑是:键不存在时,默认构造一个元素插入并返回引用。所以要触发异常,需要让自定义类型的默认构造函数抛出异常,且访问不存在的键。
示例代码:
class ThrowOnDefaultCtor { public: ThrowOnDefaultCtor() { throw std::runtime_error("Default ctor failed"); } bool operator<(const ThrowOnDefaultCtor&) const { return false; } }; TEST(MapCoverage, OperatorBracketThrow) { std::map<int, ThrowOnDefaultCtor> testMap; // 访问不存在的键,触发默认构造抛异常 EXPECT_THROW(testMap[1], std::runtime_error); EXPECT_TRUE(testMap.empty()); }
3. 触发内存分配失败的分支
如果要覆盖内存分配失败的分支,需要重载全局operator new,让它在分配map节点时抛出std::bad_alloc。这个方法比较极端,建议单独写测试用例,避免影响其他测试:
#include <new> // 临时重载全局new,强制抛异常 void* operator new(size_t) { throw std::bad_alloc(); } void operator delete(void*) noexcept {} TEST(MapCoverage, AllocationFail) { std::map<int, int> testMap; EXPECT_THROW(testMap.emplace(1, 2), std::bad_alloc); } // 恢复默认new/delete,避免影响后续测试 #undef operator new #undef operator delete
三、更高效的方案:直接过滤标准库代码
大多数场景下,我们不需要统计标准库的分支覆盖率——毕竟标准库本身已经经过严格测试。可以通过lcov的过滤规则排除标准库路径:
收集覆盖率时排除
lcov --capture --directory . --output-file coverage.info --exclude '/usr/include/*' --exclude '/usr/lib/gcc/x86_64-linux-gnu/11/*'
路径根据你的系统调整,可用g++ -v查看标准库的具体安装路径。
生成报告时过滤
如果已经收集了覆盖率数据,也可以在生成报告时过滤:
genhtml coverage.info --output-directory report --exclude '/usr/include/*'
四、额外注意点
- g版本不同,libstdc的内部实现分支可能有差异,上述方法针对g++11.4验证有效;
- 编译测试代码时建议用
-O0或-O1,避免编译器优化掉部分分支导致覆盖率统计失真; - 你提到的
map::push_back是笔误,std::map作为关联容器没有这个成员函数,应该是emplace或insert。
内容的提问来源于stack exchange,提问作者musnow
相关产品推荐
相关产品推荐

