为了通过建立和运行100%覆盖率的小型C++测试项目来了解有关自动化测试的更多信息,我遇到了以下问题-即使我的所有实际代码行和所有执行分支都已被测试覆盖,lcov仍报告两行未经测试(它们仅包含函数定义),以及“重复”的构造函数方法,即使它与我的“真实”构造函数(唯一定义和使用过的构造函数)完美匹配,也应该未经测试。

(跳至EDIT以获取最小限度的复制情况)

如果我使用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倍,另一个标记为5次) 0次)。

如果查看中间的lcov .info文件:http://gints.dyndns.info/lcov_coverage_filtered.info.txt,我也看到那里有两个构造函数定义,它们都应该位于同一行:FN:8,_ZN4BBOS8Heap_stdC1Ev和FN:8,_ZN4BBOS8Heap_stdC2Ev。

哦,不要介意.uic include/destructor周围的困惑情况,这只是处理What is the branch in the destructor reported by 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

有任何想法吗?

谢谢!

最佳答案

好吧,经过一番摸索并阅读了C++异常声明,我想我了解发生了什么:

  • 就未命中的throw声明而言,这里似乎实际上是正确的:函数throw声明应该向输出对象文件中添加额外的代码,以检查非法(就throw声明而言)异常抛出。由于我没有测试这种情况的发生,因此从未执行过该代码,并且这些语句被标记为未命中是有道理的。尽管无论如何情况都不是很理想,但是至少有人可以看到这种情况的来历。
  • 就重复的构造函数而言,这似乎是gcc的已知问题,并进行了长期讨论(并尝试了各种补丁来解决最终的目标代码重复):http://gcc.gnu.org/bugzilla/show_bug.cgi?id=3187-基本上,创建了两个版本的构造函数-一种用于本类(class),另一种用于子类(class),并且如果要100%覆盖,则需要同时进行这两种练习。
  • 09-08 11:58