在我寻求通过启动一个小型 C++ 测试项目并以 100% 的覆盖率运行来了解更多关于自动化测试的信息时,我遇到了以下问题——即使我所有的实际代码行和所有执行分支都被测试覆盖了, lcov 仍然报告两行未经测试(它们只包含函数定义),以及一个“重复”构造函数方法,即使它与我的“真实”构造函数(唯一一个曾经定义和使用过的构造函数)完美匹配,也应该是未经测试的。
(跳至编辑最小复制案例)
如果我使用 gcovr python 脚本生成相同的覆盖率统计信息(来自相同的确切来源,.gcno 和 .gcda 文件)并将结果传递给 Jenkins Cobertura 插件,它在所有方面都给了我 100% - 行、条件和方法.
这就是我的意思:
Jenkins Cobertura 覆盖页面:http://gints.dyndns.info/heap_std_gcovr_jenkins_cobertura.html(一切都是 100%)。
使用 lcov 处理的相同 .gcda 文件:http://gints.dyndns.info/heap_std_lcov.html (两个函数定义行标记为未执行,即使这些函数中的行被完全覆盖,以及函数 Hit = functions Total - 1)。
来自 lcov 的该源文件的函数统计信息:http://gints.dyndns.info/heap_std_lcov_func (显示了两个相同的构造函数定义,都引用文件中的同一行代码,其中一个标记为命中 5 次,另一个0 次)。
如果我查看中间 lcov .info 文件:http://gints.dyndns.info/lcov_coverage_filtered.info.txt我看到那里也有两个构造函数定义,两者都应该在同一行:FN:8 ,_ZN4BBOS8Heap_stdC1Ev & FN:8,_ZN4BBOS8Heap_stdC2Ev。
哦,不要介意 .uic 包含/析构函数周围的混乱,这只是处理gcov 报告的析构函数中的分支是什么的一种肮脏方式?当我拍摄这些文件快照时,我碰巧正在尝试。
有人对如何解决这个问题有建议吗?C++ 编译器在这里做了一些“幕后”魔术吗?(出于特殊目的,我应该确保从我的测试中调用的构造函数的额外副本,也许?)常规函数定义呢?即使主体已经过全面测试,定义行怎么可能未经测试?这仅仅是lcov的问题吗?欢迎提出任何建议 - 我想了解为什么会发生这种情况,以及我的测试是否确实有一些功能未被发现并且 Cobertura 没有抱怨......或者如果没有,我如何让 lcov 理解这一点?
编辑:在下面添加最小的重现场景
lcov_repro_one_bad.cpp:
#include <stdexcept>
class Parent {
public:
Parent() throw() { }
virtual void * Do_stuff(const unsigned m) throw(std::runtime_error) =0;
};
class Child : public Parent {
public:
Child() throw();
virtual void * Do_stuff(const unsigned m)
throw(std::runtime_error);
};
Child::Child()
throw()
: Parent()
{
}
void * Child::Do_stuff(const unsigned m)
throw(std::runtime_error)
{
const int a = m;
if ( a > 10 ) {
throw std::runtime_error("oops!");
}
return NULL;
}
int main()
{
Child c;
c.Do_stuff(5);
try {
c.Do_stuff(11);
}
catch ( const std::runtime_error & ) { }
return 0;
}
生成文件:
GPP_FLAGS:=-fprofile-arcs -ftest-coverage -pedantic -pedantic-errors -W -Wall -Wextra -Werror -g -O0
all:
g++ ${GPP_FLAGS} lcov_repro_one_bad.cpp -o lcov_repro_one_bad
./lcov_repro_one_bad
lcov --capture --directory ${PWD} --output-file lcov_coverage_all.info --base-directory ${PWD}
lcov --output-file lcov_coverage_filtered.info --extract lcov_coverage_all.info ${PWD}/*.*
genhtml --output-directory lcov_coverage_html lcov_coverage_filtered.info --demangle-cpp --sort --legend --highlight
这是我从中得到的报道: http: //gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_bad.cpp.gcov.html
如您所见,所谓的未命中行是函数可能抛出的异常的定义,并且 Child 的额外未命中构造函数仍然存在于函数列表中(单击顶部的函数)。
我尝试从函数定义中删除 throw 声明,这会处理函数声明中未执行的行:http: //gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_v1.cpp。 gcov.html(如您所见,额外的构造函数仍然存在)。
我尝试将函数定义移动到类主体中,而不是稍后定义它们,这样就摆脱了额外的构造函数:http: //gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_v2.cpp。 gcov.html(不过,Do_stuff 函数定义仍有一些奇怪之处,如您所见)。
然后,当然,如果我同时做上述两个,一切都很好: http: //gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_ok.cpp.gcov.html
但我仍然不知道这是什么根本原因......而且我仍然希望将我的方法(包括构造函数)定义在一个单独的 .cpp 文件中,而不是在类主体中,我确实想要我的具有明确定义的异常的函数可以抛出!
这是源代码,以防你想玩这个: http: //gints.dyndns.info/lcov_repro_src.zip
有任何想法吗?
谢谢!