AnSwErYWJ's Blog

C代码覆盖率测试工具Gcov

字数统计: 1.8k阅读时长: 40 min
2018/09/25

代码覆盖率测试反映了测试的广度与深度,量化了测试和开发质量,是十分有必要的,业界目前有针对各种语言的覆盖率测试工具,本文主要介绍C/C++相关的覆盖率测试工具Gcov

介绍

简介

Gcov是一个测试覆盖程序,是集成在GCC中的,随GCC一起发布

基本概念

基本块BB

基本块指一段程序的第一条语句被执行过一次后,这段程序中的每一跳语句都需要执行一次,称为基本块,因此基本块中的所有语句的执行次数是相同的,一般由多个顺序执行语句后边跟一个跳转语句组成

跳转ARC

从一个BB到另外一个BB的跳转叫做一个ARC,要想知道程序中的每个语句和分支的执行次数,就必须知道每个BBARC的执行次数

程序流图

如果把BB作为一个节点,这样一个函数中的所有BB就构成了一个有向图,要想知道程序中的每个语句和分支的执行次数,就必须知道每个BBARC的执行次数,根据图论可以知道有向图中BB的入度和出度是相同的,所以只要知道了部分的BB或者ARC大小,就可以推断所有的大小,这里选择由ARC的执行次数来推断BB的执行次数,所以对部分ARC插桩,只要满足可以统计出来所有的BBARC的执行次数即可

原理

测试程序首先进行编译预处理,生成汇编文件,并完成插桩,插桩的过程中会向源文件的末尾插入一个静态数组,数组的大小就是这个源文件中桩点的个数,数组的值就是桩点的执行次数,每个桩点插入3~4条汇编语句,直接插入生成的*.s文件中,最后汇编文件经过汇编生成目标文件,在程序运行过程中桩点负责收集程序的执行信息

使用

编译

测试代码如下:
say.c:

1
2
3
4
5
6
#include <stdio.h>

int say(char *what) {
printf("------ %s\n", what);
return 0;
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

extern int say(const char *);

int main(int argc, const char *argv[]) {

if (argv[1]) {
say("hello");
} else {
say("bye");
}
return 0;
}

添加-fprofile-arcs -ftest-coverage -fPIC编译参数编译程序,生成可执行程序和*.gcno文件,里面记录了行信息和程序流图信息:

1
2
3
4
$ gcc -fprofile-arcs -ftest-coverage -fPIC -O0 say.c main.c

$ ls
a.out main.c main.gcno say.c say.gcno

数据收集

运行可执行文件,生成*.gcda在默认生成在相应*.o文件目录,里面记录了*.c文件中程序的执行情况,包括跳变次数等:

1
2
3
4
5
$ ./a.out
------ bye

$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno

可以通过设置环境变量GCOV_PREFIX=/xxx/xxxGCOV_PREFIX_STRIP=x来改变路径,其中GCOV_PREFIX_STRIP表示去掉源代码路径中的前几级,默认为0,比如源代码路径为/a/b/c/d.cGCOV_PREFIX_STRIP=2,则实际使用的路径是c/d.c,如果GCOV_PREFIX=/e/f,则.gcda实际存放的路径是/e/f/c/d.gcda

报告生成

针对某一个文件的执行情况,可以通过如下命令生成报告,并创建*.gcov文件:

1
2
3
4
$ gcov -a main.c
File 'main.c'
Lines executed:80.00% of 5
Creating 'main.c.gcov'

常用选项,更多可参考Invoking gcov

1
2
3
-b:分支覆盖
-a:所有基本块覆盖
-f:函数覆盖

注意事项

  1. 在编译时不要加优化选项,否则代码会发生变化,无法准确定位
  2. 代码中复杂的宏,比如宏展开后是循环或者其他控制结构,可以用内联函数来代替,因为gcov只统计宏调用出现的那一行
  3. 代码每一行最好只有一条语句
  4. *.gcno*.gcda需要匹配,两个文件是有时间戳来记录是不是匹配的
  5. 若是编译动态库,需要在链接时-lgcov

图形化展示

gcov生成的报告分散在各个源码文件所对应的*.gcov文件中,难以汇总分析,并且可视化效果较差,所以需要转化成可视图形化报告,有lcovgcovr两个工具可以完成,两者功能基本相同,本文主要介绍gcovr,是一个用Python编写的开源软件,大小只有几十KB,安装参见官网

列表形式

  1. 代码覆盖率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ gcovr -r .
    ------------------------------------------------------------------------------
    GCC Code Coverage Report
    Directory: .
    ------------------------------------------------------------------------------
    File Lines Exec Cover Missing
    ------------------------------------------------------------------------------
    main.c 5 4 80% 15
    say.c 3 3 100%
    ------------------------------------------------------------------------------
    TOTAL 8 7 87%
    ------------------------------------------------------------------------------

    报告展示程序运行后覆盖了80%的代码

  2. 分支覆盖率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ gcovr -b -r .
    ------------------------------------------------------------------------------
    GCC Code Coverage Report
    Directory: .
    ------------------------------------------------------------------------------
    File Branches Taken Cover Missing
    ------------------------------------------------------------------------------
    main.c 2 1 50% 14
    say.c 0 0 --%
    ------------------------------------------------------------------------------
    TOTAL 2 1 50%
    ------------------------------------------------------------------------------

    报告展示了在main.c中有一个分支没有执行到

XML文件形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ gcovr --xml-pretty -r .
<?xml version="1.0" ?>
<!DOCTYPE coverage
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'>
<coverage branch-rate="0.5" branches-covered="1" branches-valid="2"
complexity="0.0" line-rate="0.875" lines-covered="7" lines-valid="8"
timestamp="1537930892" version="gcovr 3.4">
<sources>
<source>.</source>
</sources>
<packages>
<package branch-rate="0.5" complexity="0.0" line-rate="0.875" name="">
<classes>
<class branch-rate="0.5" complexity="0.0" filename="main.c"
line-rate="0.8" name="main_c">
<methods/>
<lines>
<line branch="false" hits="1" number="12"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" number="14">
<conditions>
<condition coverage="50%" number="0" type="jump"/>
</conditions>
</line>
<line branch="false" hits="0" number="15"/>
<line branch="false" hits="1" number="17"/>
<line branch="false" hits="1" number="19"/>
</lines>
</class>
<class branch-rate="0.0" complexity="0.0" filename="say.c" line-rate="1.0"
name="say_c">
<methods/>
<lines>
<line branch="false" hits="1" number="10"/>
<line branch="false" hits="1" number="11"/>
<line branch="false" hits="1" number="12"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>

HTML文件形式

1
2
3
$ gcovr -r . --html -o xxx.html
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno xxx.html

可以发现添加--html参数后,可以生成html文件,用浏览器打开,如下图:
gcovr_xxx.png

还可以添加--html-details选项,为每个代码文件单独生成html

1
2
3
$ gcovr -r . --html --html-details -o xxx.html
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno xxx.html xxx.main.c.html xxx.say.c.html

可以发现多了xxx.main.c.htmlxxx.say.c.html,用浏览器打开xxx.html,如下图:
gcovr_xxx_detail.png
文件名较之前带上了下划线,单击文件名,可以看到具体的代码覆盖情况,如下图:
gcovr_xxx_main.png

其它

其它功能,如Filters等,可以参考官方文档

Reference

原文作者:AnSwErYWJ

原文链接:https://answerywj.com/2018/09/25/coverage-of-code/

发表日期:2018/09/25 18:09

版权声明:本文采用Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License进行许可.
Creative Commons License

CATALOG
  1. 1. 介绍
    1. 1.1. 简介
    2. 1.2. 基本概念
      1. 1.2.1. 基本块BB
      2. 1.2.2. 跳转ARC
      3. 1.2.3. 程序流图
    3. 1.3. 原理
  2. 2. 使用
    1. 2.1. 编译
    2. 2.2. 数据收集
    3. 2.3. 报告生成
    4. 2.4. 注意事项
    5. 2.5. 图形化展示
      1. 2.5.1. 列表形式
      2. 2.5.2. XML文件形式
      3. 2.5.3. HTML文件形式
      4. 2.5.4. 其它
  3. 3. Reference