【C++】单元测试覆盖率工具lcov的使用

2024-08-28 09:12

本文主要是介绍【C++】单元测试覆盖率工具lcov的使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文首发于 ❄️慕雪的寒舍

本文讲述了如何在C++代码中使用单元测试覆盖率工具lcov,以及gcov命令的使用。版本是lcov 2.0gcov 11.4.0

写在前面:lcov是我在实习期间初次接触到的工具,当时在配置的时候就遇到了大量中文互联网没有任何记录的问题。绝大部分博客对lcov工具的介绍仅停留在安装,并没有对它的使用和报告分析做出更进一步的详解,这也是慕雪撰写本文的原因。希望这篇文章能对需要使用lcov工具却又苦于没有引导教程的老哥提供一丝丝帮助。

1. 安装

安装lcov的方式比较简单,去github上下载官方的安装包就可以了。

# ubuntu安装依赖项
sudo apt-get install -y perl libcapture-tiny-perl libdatetime-perl libdatetime-format-dateparse-perl wget
# 下载
wget https://github.com/linux-test-project/lcov/releases/download/v2.0/lcov-2.0.tar.gz
# 解压
tar -zxvf lcov-2.0.tar.gz
cd lcov-2.0
# 安装
sudo make install

安装完毕后查看版本号,成功出现版本号则代表安装成功。

❯ lcov --version
lcov: LCOV version 2.0-1

更详细的lcov安装教程详见本站【Linux】lcov2.0安装和perl修改镜像源一文。另外,本文演示所用的单元测试框架Gtest也建议安装一下。需要说明的是,lcov的报告并不依赖于Gtest或任何测试框架,只要函数被调用、代码被运行了,它就可以生成覆盖率报告。

2. 基本命令

2.1. 手工执行

lcov的基本使用方式如下:

首先我们需要用g++命令编译gtest写出来的单元测试代码,使用-lgtest -lgtest_main -pthread链接gtest库和pthread库。选项-ftest-coverage可以让g++编译器在代码中插入额外的指令,来确认某部分的代码是否执行了,一般要和-fprofile-arcs连用才能产生完整的覆盖率报告。

程序运行后会产生.gcda.gcov.gcno文件,记录了覆盖率信息,lcov依赖于这些文件产生最终的html覆盖率报告。

g++ -std=c++17 test.cpp -o test \-lgtest -lgtest_main -pthread \-fprofile-arcs -ftest-coverage -fprofile-update=atomic

g++命令最后的-fprofile-update=atomic是lcov 2.0中需要新增的一个编译选项,否则运行lcov的时候会有告警(具体记不清了,最初的记录里面忘记写这一块的内容了)。

使用如上方式编译了单元测试的代码了之后,就可以执行lcov命令来生成报告了

lcov --capture \--rc branch_coverage=1 \--directory . \--output-file coverage.info \--ignore-errors mismatch

这个命令最终会生成一个coverage.info信息文件。其中--rc branch_coverage=1是用于开启分支检测的,不指定这个选项,输出的文件中将不包含分支覆盖率信息,只会有行覆盖率信息。选项--ignore-errors mismatch是因为lcov 2.0版本出现了一些问题,经常会找不到某些函数的符号表(不知道啥情况,lcov 1.6没有此告警),会有mismatch错误,需要将其忽略。

生成了coverage.info文件之后,再使用genhtml命令将其转化为最终的html报告,输出到coverage_report目录中。

genhtml coverage.info \--rc branch_coverage=1 \--output-directory coverage_report

一切顺利的话,执行了这些命令,你就可以在当前目录下的coverage_report子目录中找到lcov的html报告了。

2.2. makefile

我们可以把上述命令写入一个makefile中,这样可以方便我们执行命令。更新了测试源码之后,使用make locv就可以生成最新的覆盖率报告。

test:test.cppg++ -std=c++17 test.cpp -o test -lgtest -lgtest_main -pthread -fprofile-arcs -ftest-coverage -fprofile-update=atomiclcov:test.cppg++ -std=c++17 test.cpp -o test -lgtest -lgtest_main -pthread -fprofile-arcs -ftest-coverage -fprofile-update=atomic && \./test && \gcov -b -c -o . test.cpp && \lcov --capture \--rc branch_coverage=1 \--directory . \--output-file coverage_all.info \--ignore-errors mismatch && \genhtml coverage.info \--rc branch_coverage=1 \--ignore-errors mismatch \--output-directory coverage_report && \rm *.info.PHONY:cl
cl:sudo rm -rf test *.gcno *.gcda *.gcov out

3. Demo演示

3.1. 基本demo

下面是一个最简单的C++代码,以及对应的测试处理,首先在main.hpp里面定义了一个最基础的相减函数

// 相减函数
int Sub(int a, int b)
{if (a > b){return a - b;}return b - a;
}

随后,在test.cpp中引用这个头文件并调用Sub函数

#include <gtest/gtest.h>
#include "main.hpp"TEST(SubTest, SubTest1)
{EXPECT_EQ(Sub(3, 2), 1);  // 期望 result 等于 1
}int main(int argc, char **argv)
{::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

使用上文提到的命令,编译和创建lcov报告。在lcov命令的最后输出中,会包含如下覆盖率信息

Overall coverage rate:lines......: 50.3% (83 of 165 lines)functions......: 46.3% (44 of 95 functions)branches......: 42.9% (24 of 56 branches)

因为我用的是WSL2,可以方便的直接打开报告生成目录,查看index.html文件。

image.png

可以看到,报告中列出了所有涉及到的文件,以及这些文件的行覆盖率,分支覆盖率,执行次数。

image.png

但是!这里面有大量C++库函数以及gtest库的代码,我们自己的代码反而被掩盖过去了,这肯定不是我们想要的结果。毕竟不是自己写的代码都不需要测试覆盖率。所以我们需要做点操作,屏蔽掉所有库函数的报告。

将上文的lcov命令的最终输出文件改成coverage_all.info

lcov --capture \--rc branch_coverage=1 \--directory . \--output-file coverage_all.info \--ignore-errors mismatch

然后在genhtml命令之前,执行如下命令。这个命令会处理原本生成的全量数据,把里面我们不想要的东西全都删除掉,再生成一个coverage.info文件。

lcov --remove coverage_all.info \'*/usr/include/*' '*/usr/lib/*' '*/usr/lib64/*' \'*/usr/local/include/*' '*/usr/local/lib/*' '*/usr/local/lib64/*' \--rc branch_coverage=1 \--output-file coverage.info \--ignore-errors unused \--ignore-errors mismatch 

随后再执行genhtml命令,这一次生成的报告文件就只有我们自己的代码了。

genhtml coverage.info \--rc branch_coverage=1 \--output-directory coverage_report

image.png

进入报告中看,其实这里还是有一个需要排除的项目的,即test.cpp是单元测试的文件,我们也不需要关注单元测试这个文件本身的覆盖率正常不,当前我们只需要关注main.hpp这个功能源码文件的覆盖率。

可以将test.cpp也写入上文的--remove选项之后,这样它也会被过滤掉。实际项目中,直接过滤单元测试代码文件的目录即可。

另外,使用这个命令,lcov会在输出中报告没有被匹配上的地址,可以用--ignore-errors unused来屏蔽这个告警。

lcov: WARNING: ('unused') 'exclude' pattern '*/usr/lib/*' is unused.
lcov: WARNING: ('unused') 'exclude' pattern '*/usr/lib64/*' is unused.(use "lcov --ignore-errors unused,unused ..." to suppress this warning)
lcov: WARNING: ('unused') 'exclude' pattern '*/usr/local/lib/*' is unused.(use "lcov --ignore-errors unused,unused ..." to suppress this warning)
lcov: WARNING: ('unused') 'exclude' pattern '*/usr/local/lib64/*' is unused.(use "lcov --ignore-errors unused,unused ..." to suppress this warning)

3.2. 报告基本分析

image.png

点开main.hpp文件,可以看到如下报告。其中右上角是当前文件的覆盖率信息,然后会展示文件的源码:

  • 源码每一行之前的数字是这一行被运行了几次;
  • 底色为橙色标注的,就是没有被覆盖的行;
  • 蓝色标注的,则是被覆盖了的行;

在每一个if语句的分支点,也会产生一个分支覆盖率报告,这里显示的[+,-]代表if条件为true的分支被命中了,为false的分支没有命中。在测试代码中我使用的是Sub(3, 2)来调用该函数,参数a是大于b的(命中true分支),也和这里的分支覆盖报告相符。

image.png

这样我们就可以知道,当前需要怎么补充测试用例了。我们需要补充一个b比a大或者相等的测试用例,追加如下测试调用。

#include <gtest/gtest.h>
#include "main.hpp"TEST(SubTest, SubTest1)
{EXPECT_EQ(Sub(3, 2), 1);  // 期望 result 等于 1EXPECT_EQ(Sub(2, 4), 2);  // 期望 result 等于 2EXPECT_EQ(Sub(3, 3), 0);  // 期望 result 等于 0
}int main(int argc, char **argv)
{::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

再次编译执行,重新查看报告。此时可以看到,我们的main.hpp已经实现了100%的覆盖。

image.png

删除其他测试,留一个b比a大的测试用例。此时可以看到,分支覆盖显示为[-,+],代表我们当前分支的false条件被命中了,但是true条件没有。

image.png

简单总结,单个分支覆盖率中[]的逗号左侧是true,右侧是false;+代表覆盖,-代表没有覆盖,#代表这个分支没有被执行。

3.3. 多条件判断

上面的if语句中我们只写了一个判断条件,实际场景中判断条件不止一个的情况还是经常出现的,给Sub函数新增一个参数,再来进行测试。

#include <cstdbool>
// 相减函数,默认是A-B,第三个参数为是否要返回绝对值
int Sub(int a, int b, bool isAbs)
{if (b < a && isAbs){return b - a;}return a - b;
}

测试用例如下

#include <gtest/gtest.h>
#include "main.hpp"TEST(SubTest, SubTest1)
{// 传入false代表我们想a-b,不需要绝对值EXPECT_EQ(Sub(2, 4, false), -2);  // 期望 result 等于 -2
}int main(int argc, char **argv)
{::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

这里可以看到,在if语句左侧的分支覆盖率条件的[]里面多了一对加减,这里多的就是isAbs这个判断条件,分支覆盖率中的每一个判断条件都有一个true/false分支,两两一对,从左到右的顺序和我们的判断条件中的条件顺序是一致的。

在上面的测试用例中,我们传入了b大于a的值,同时isAbs是false,命中了b > a为true的分支,和isAbs为false的分支。

image.png

新增一个测试用例,这一次命中的是isAbs为true的分支。

	EXPECT_EQ(Sub(2, 4, true), 2);

报告中,isAbs为true的分支也变成了+代表已命中,符合预期。

image.png

再添加一个a比b大的测试用例,即可将该函数的所有分支覆盖完毕。

	EXPECT_EQ(Sub(6, 4, true), 2);

image.png

4. 引入gcov命令

4.1. 基本使用

接下来给大家引入gcov命令的使用,gcov命令可以生成更加详细的关于某个分支为什么没有被覆盖的说明。比如未覆盖的异常分支在生成的源文件.gcov文件中就会显示出来。

gcov命令和gcc/g++是同源的,只要你的系统上安装了gcc,那就会有gcov命令。二者的版本号输出都是一致的。

❯ gcov --version          
gcov (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    
❯ gcc --version 
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

执行过lcov命令后,在构建目录下会产生很多的.gcov文件,和我们自己的代码的.gcda文件

ls
alloc_traits.h.gcov    gtest-assertion-result.h.gcov  main.hpp              stl_iterator_base_funcs.h.gcov  test.gcda
basic_string.h.gcov    gtest.h.gcov                   main.hpp.gcov         stl_iterator_base_types.h.gcov  test.gcno
basic_string.tcc.gcov  gtest-internal.h.gcov          makefile              test                            tuple.gcov
char_traits.h.gcov     gtest-port.h.gcov              move.h.gcov           test.cpp                        type_traits.h.gcov
coverage_report        gtest-printers.h.gcov          new_allocator.h.gcov  test.cpp.gcov                   unique_ptr.h.gcov

我们可以使用gcov命令,把这些文件文件转换成可读报告。

gcov -b -c -o .gcda文件所在路径 cpp源文件路径

比如这里我们要处理的是test.gcda,命令如下。

gcov -b -c -o . test.cpp

这个命令会生成一个源文件名.gcov文件,文件中就会有详细的文字说明了。比如main.hpp.gcov文件中的描述如下,有每一个分支被命中的次数,后面有个括号是这个分支的说明。

文件中第一列的-:代表这一行不统计命中次数,第一列数字:代表这一行被执行的次数(注意要和第二列的代码行号区分开)。

        -:    0:Source:main.hpp-:    0:Graph:./test.gcno-:    0:Data:./test.gcda-:    0:Runs:1-:    1:#include <cstdbool>-:    2:// 相减函数,默认是A-B,第三个参数为是否要返回绝对值
function _Z3Subiib called 3 returned 100% blocks executed 100%3:    3:int Sub(int a, int b, bool isAbs)-:    4:{3:    5:    if (b > a && isAbs)
branch  0 taken 2 (fallthrough)
branch  1 taken 1
branch  2 taken 1 (fallthrough)
branch  3 taken 1-:    6:    {1:    7:        return b - a;-:    8:    }2:    9:    return a - b;-:   10:}

文件中的(fallthrough)代表当前if分支被跳过。比如下面的代码中,fallthrough的意思就是当a不等于b的时候,分支A会被跳过,走到分支B中。

if (a == b){// A
} else {// B
}

在上面的例子中,当b小于等于a或者isAbs为假的时候,return b - a;就会被跳过,落到return a - b;分支中。

这里将isAbs改成一个函数调用,函数本身参数不变

#include <cstdbool>
#include <exception>bool isAbsFunc(int a, int b, bool isAbs)
{// 当a为100的时候抛出异常if (a == 100){throw std::invalid_argument("a should not be 100.");}return isAbs;
}// 相减函数,默认是A-B,第三个参数为是否要返回绝对值
int Sub(int a, int b, bool isAbs)
{if (b > a && isAbsFunc(a, b, isAbs)){return b - a;}return a - b;
}

此时分支覆盖率报告如下。因为我们当前的a并不等于100,所以一直命中的都是a == 100为false的分支,符合预期。但是这里会有一个额外的分支未覆盖情况,即throw这一行也出现了一个分支,且[]里面的两个符号都是#代表这一行没有被运行

image.png

我们可以用gcov命令来看看throw这一行的分支覆盖情况。可以看到这里提示never executed,没有运行。

        -:    0:Source:main.hpp-:    0:Graph:./test.gcno-:    0:Data:./test.gcda-:    0:Runs:1-:    1:#include <cstdbool>-:    2:#include <exception>-:    3:
function _Z9isAbsFunciib called 2 returned 100% blocks executed 50%2:    4:bool isAbsFunc(int a, int b, bool isAbs)-:    5:{-:    6:    // 当a为100的时候抛出异常2:    7:    if (a == 100)
branch  0 taken 0 (fallthrough)
branch  1 taken 2-:    8:    {#####:    9:        throw std::invalid_argument("a should not be 100.");
call    0 never executed
call    1 never executed
branch  2 never executed
branch  3 never executed
call    4 never executed
call    5 never executed-:   10:    }2:   11:    return isAbs;-:   12:}-:   13:-:   14:// 相减函数,默认是A-B,第三个参数为是否要返回绝对值
function _Z3Subiib called 3 returned 100% blocks executed 100%3:   15:int Sub(int a, int b, bool isAbs)-:   16:{3:   17:    if (b > a && isAbsFunc(a, b, isAbs))
branch  0 taken 2 (fallthrough)
branch  1 taken 1
call    2 returned 2
branch  3 taken 1 (fallthrough)
branch  4 taken 1
branch  5 taken 1 (fallthrough)
branch  6 taken 2-:   18:    {1:   19:        return b - a;-:   20:    }2:   21:    return a - b;-:   22:}

那我们加一个a等于100的测试用例呢?

    // 期望抛出异常EXPECT_ANY_THROW(Sub(100, 400, true));

此时gcov文件会是如下模样,我们a == 100的两个分支都命中了,但是你会发现,它有一个branch 3 taken 0 (throw)为0次命中,没有被覆盖上。

function _Z9isAbsFunciib called 3 returned 67% blocks executed 88%3:    4:bool isAbsFunc(int a, int b, bool isAbs)-:    5:{-:    6:    // 当a为100的时候抛出异常3:    7:    if (a == 100)
branch  0 taken 1 (fallthrough)
branch  1 taken 2-:    8:    {1:    9:        throw std::invalid_argument("a should not be 100.");
call    0 returned 1
call    1 returned 1
branch  2 taken 1 (fallthrough)
branch  3 taken 0 (throw)
call    4 returned 0
call    5 never executed-:   10:    }2:   11:    return isAbs;-:   12:}

在lcov报告中也是如此,会显示有一个没有覆盖的(throw)抛异常分支。

image.png

4.2. lcov过滤std库函数造成的分支

这就涉及到lcov的一个不那么容易找到的设置了,当时百度了老久,最后还是去Github翻issue才得到的答案。下面贴出几个相关的issue

  • https://github.com/linux-test-project/lcov/issues/101;
  • https://github.com/linux-test-project/lcov/issues/108;
  • https://github.com/linux-test-project/lcov/issues/75;

简而言之,lcov支持过滤掉这类由std库造成的无法覆盖的异常分支。只需要在lcov命令和genhtml命令中加上--filter branch选项即可。添加了这个命令后,可以看到throw这一行的分支被过滤不显示了。

image.png

即便我们没有命中a == 100的情况,throw这一行也不会出现[##]的未命中分支。

image.png

再举个map的emplace的例子,代码如下

void EmplaceMap(int key, int value)
{static std::map<int, std::set<int>> mapValue;mapValue[key].emplace(value);
}

测试代码

TEST(EmplaceMapTest,EmplaceMapTest1)
{EXPECT_NO_THROW(EmplaceMap(1,2));EXPECT_NO_THROW(EmplaceMap(1,3));
}

可以看到,如果不加上--filter branch过滤选项,在lcov报告中,即便这一行是完全不存在任何分支的,也会出现一个未覆盖的情况。

image.png

生成的gcov文件如下,这里会有一个不知道什么由来的branch 4没有被覆盖到。

function _Z10EmplaceMapii called 2 returned 100% blocks executed 100%2:    6:void EmplaceMap(int key, int value)-:    7:{2:    8:    static std::map<int, std::set<int>> mapValue;
branch  0 taken 1 (fallthrough)
branch  1 taken 1
call    2 returned 1
branch  3 taken 1 (fallthrough)
branch  4 taken 0
call    5 returned 1
call    6 returned 1
call    7 returned 12:    9:    mapValue[key].emplace(value);
call    0 returned 2
call    1 returned 22:   10:}-:   11:

加上了--filter branch过滤选项之后,这一行则完全不会有分支覆盖率信息。这才是我们预期的输出,因为我们不应该关注不是我们自己写的代码(比如std库和第三方库)中的分支。

image.png

另外,过滤选项默认只对常见的cpp头文件起效。对诸如.inl这种头文件是不起效果的。可以使用如下命令,修改默认的c_file_extensions后缀名配置,添加你需要的文件后缀。

--rc c_file_extensions=c,cpp,hpp,h,inl

关联issue:https://github.com/linux-test-project/lcov/issues/250。

5. 一些lcov报告问题的记录

经过上面的步骤,想必你已经知道怎么去使用lcov了。下面是我在使用lcov过程中遇到的一些报告的共性问题,记录于此,经供参考。

5.1. lcov屏蔽语法

lcov本身也支持通过在代码中添加注释的方式来屏蔽一些代码的覆盖率检测。屏蔽的语法分为单行代码和多行屏蔽。

// LCOV_EXCL_BR_START多行代码
// LCOV_EXCL_BR_STOP单行代码 // LCOV_EXCL_BR_LINE

在实际代码中,可能会有一些linux库函数调用这类难以复现失败场景的函数调用,又没有办法被过滤掉的分支。这种情况就可以在注明原因以后,使用lcov的屏蔽注释将其屏蔽掉,让最终生成的报告里面没有这些难以覆盖的错误情况。

5.2. assert假分支无法覆盖

如下图所示,lcov的assert始终只会覆盖假的分支,因为分支为真的时候就直接程序终止了。

image.png

Gtest中有一个EXPECT_DEATH可以用来测试assert为真的情况,但即便使用了这个宏,lcov和gcov依旧无法生成命中的报告。所以,推荐的做法是在编译单元测试代码的时候使用-DNDEBUG宏直接禁用所有assert,这样就不会有关于assert的分支覆盖率报告了。

5.3. trylock分支覆盖

一般情况下,在我们的测试场景中不太好复现try_lock()函数调用失败的分支,这需要有一个多线程的场景,但多线程操作共享资源的运行顺序本身就是不可预知的,不太好在单元测试中构建出一个一定冲突的场景来。

image.png

这时候可以用一种黑魔法,在单元测试中,取出类的私有成员变量mutex,将其lock了之后,再去调用包含try_lock()调用和判断的函数。函数调用完毕后,再unlock解锁。

如下是这个黑魔法的源码和使用示例,注意只有g++使用-std=c++17之后才支持编译这个代码。在windows的vs2019下这个特性是编译不过的,即便设置了C++17也不行,可能是我的配置不对。

#pragma once
// C++17才支持,通过友元和元组,取出任意成员变量
template <class T, auto... Member>
struct StealMember
{friend auto StealClass(T &t){return std::make_tuple(Member...);}
};// 使用示例:
// // 1.友元函数声明,TestClass是我们需要操作的目标类。
auto StealClass(TestClass &t);
// // 2.在下面的模板中添加需要的私有函数或成员。
template class StealMember<TestClass,&TestClass::GetA, &TestClass::_a, &TestClass::_b>;
// // 3.在需要的函数中使用如下方式取成员变量。
void TestFunc() {TestClass t1(20, 300.23);auto tp = StealClass(t1); // 构建元组cout << "GetA: " << (t1.*(std::get<0>(tp)))() << endl;cout << "_a:   " << (t1.*(std::get<1>(tp))) << endl;
}

这个代码首先声明了友元函数,StealClass会被声明成TestClass的友元函数,从而可以读取到该类的私有成员。随后在实例化StealMember模板的时候,指定了目标类和其私有成员,这样StealClass函数就可以在调用的时候,给我们返回一个包含私有成员指针的元组

重点来了:C++在实例化模板的时候,不会去检查成员的访问限定符。

有了元组,就可以用std::get<元组内元素下标>(元组对象)的方式取出元组的某一个成员,即私有成员的指针。有了私有成员的指针之后,我们就可以使用对象.(*私有成员指针)的方式访问到一个私有成员变量或者成员函数了。

关于这个特性的更多介绍,可以参考下面的资料

  • 【C++】C++私有成员劫持技巧-哔哩哔哩;
  • 访问私有成员——从技术实现的角度破坏"封装" 性;

咋样,是不是很“黑魔法”呢?

5.4. string相加的时候会有大量无法覆盖的异常分支

如下图所示,这个函数中调用了string的相加操作,造成了大量的没有覆盖的异常分支。

image.png

在gcov报告中可以更详细的看到没有被覆盖的分支都是什么,大多都是和throw有关的。

function _Z16test_string_plusRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES6_ called 2 returned 100% blocks executed 69%2:   48:void test_string_plus(const string& local ,const string& remote)-:   49:{2:   50:    static string recv_msg;
branch  0 taken 1 (fallthrough)
branch  1 taken 1
call    2 returned 1
branch  3 taken 1 (fallthrough)
branch  4 taken 0
call    5 returned 1
call    6 returned 1
call    7 returned 12:   51:    static_cast<void>(recv_msg.assign("/" + (local < remote ? local + "_" + remote : remote + "_" + local)));
call    0 returned 2
branch  1 taken 1 (fallthrough)
branch  2 taken 1
call    3 returned 1
branch  4 taken 1 (fallthrough)
branch  5 taken 0 (throw)
call    6 returned 1
branch  7 taken 1 (fallthrough)
branch  8 taken 0 (throw)
call    9 returned 1
branch 10 taken 1 (fallthrough)
branch 11 taken 0 (throw)
call   12 returned 1
branch 13 taken 1 (fallthrough)
branch 14 taken 0 (throw)
call   15 returned 2
branch 16 taken 2 (fallthrough)
branch 17 taken 0 (throw)
call   18 returned 2
call   19 returned 2
call   20 returned 2
branch 21 taken 1 (fallthrough)
branch 22 taken 1
call   23 returned 1
branch 24 taken 1 (fallthrough)
branch 25 taken 1
call   26 returned 1
call   27 never executed
branch 28 never executed
branch 29 never executed
call   30 never executed
branch 31 never executed
branch 32 never executed
call   33 never executed2:   52:}

可当前我已经添加了过滤命令了,为什么没有生效呢?

实际上,将上面的代码改成下面的if/else逻辑,就不会有这么多的异常分支了。

image.png

这是我的猜想:lcov的过滤命令在检测到某一行中有用户定义的判断条件local < remote的时候就会失效,因为可能会错误过滤掉用户自己的分支。与其错报一万不可少报一个,于是就把所有的异常分支都展现出来了。

三目运算符改成if/else了之后,用户定义的判断条件和string的相加操作隔离开了,就能正常进行过滤了。所以,在编写优化分支覆盖率的代码的时候,可以考虑将

6. The end

其实在最开始的时候我记录了更多lcov相关的错误,但大部分错误都可以使用--filter branch选项过滤掉,且有一部分错误在我当前的环境中并没有被复现出来,故此不记录于本文中。

如果你遇到了本文没有记录的问题,欢迎在评论区留言交流。

这篇关于【C++】单元测试覆盖率工具lcov的使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1114345

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只