You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:16:30