为什么需要代码覆盖率分析?

在发布代码的时候,我们常常会对其进行一系列的测试来协调软件的性能和功能,使他们和预计的相同。但是检验通常都是相当的困难,即使程序相当的简单。开发者常常会借助一些测试工具(test suite)来模拟或者重建执行脚本。如果测试程序组是彻底的,那么程序的各个功能都将被测试到并且都可以证明是可以工作的。

但是怎样才算彻底呢?简单点说就是测试程序的每一条路径,验证每一个结果,执行每一条语句,证明没一句语句是没用的。gcov就是一个用来检验你的每一句语句是否都执行了的工具。

什么是代码覆盖率分析?
代码覆盖率分析就是找到定位没用的或者不执行的代码的过程。没用的代码不会存在什么问题,但是他们会影响程序的可读性;不执行的代码则可能是未来bug的所在。所以找到他们,把他们从你的程序中移处是大有裨益的。
覆盖率分析主要有下面的几个过程:
    通过测试程序组找到不执行的程序段;
    添加额外测试程序组,以便增加代码覆盖率;
    决定代码覆盖率的定量测度,他也是程序质量的间接测度。

代码覆盖率分析的缺陷
代码覆盖率分析不能找出程序的逻辑错误。考虑一下下面的代码

10:  rc = call_to_xx (); 

11:  if (rc == ERROR_FATAL) 

12:    exit(2);    /* exit with error code 2 */ 

13:  else 

14:    /* continue on */


当测试程序段运行到11行时,11行始终都不能为真。call_to_xx返回了另外的一个错误比如ERROR_HANDLE,除非我们加入这种错误的处理方式的代码。
代码覆盖测试工具不会告诉你什么是必须的,他们只能显示已经存在的代码的覆盖率。

代码覆盖率的类型
gcov可以用来测量各种形式的代码覆盖率。最常见最有用的两种是分支覆盖(branch coverage)和循环覆盖(loop coverage)
分支覆盖证明各个方向的每一条分支都被执行到了。循环覆盖试图证明循环内部的每一条路径都被测试到了。循环覆盖似乎非常的复杂,但基本上只要满足下面的三个状况,就可以作了。
    1。循环条件不满足,循环没有内部没有执行;
    2。循环条件就满足了一次,循环内部就执行了一次;
    3。循环条件至少满足了两次,循环至少执行了两次。
举个例子

void  function(int number) 

{ 

  if (number % 2) == 0) 

    printf("even /n"); 

  for (;number < 9; number++){ 

    printf("number is %d/n", number); 

  } 

} 


function(11);   满足状况一 

function(8);    满足状况二 

function(6);    满足状况三



代码覆盖率工具gcov的使用
要使用gcov,需要在我们用gcc编译程序时加入两个参数fprofile-arcs和ftest-coverage.
fprofile-arcs参数使gcc创建一个程序的流图,之后找到适合图的生成树。只有不在生成树中的弧被操纵(instrumented):gcc添加了代码来清点这些弧执行的次数。当这段弧是一个块的唯一出口或入口时,操纵工具代码(instrumentation code)将会添加到块中,否则创建一个基础块来包含操纵工具代码。
gcov主要使用.gcno和.gcda两个文件
.gcno是由-ftest-coverage产生的,它包含了重建基本块图和相应的块的源码的行号的信息。
.gcda是由加了-fprofile-arcs编译参数的编译后的文件运行所产生的,它包含了弧跳变的次数和其他的概要信息。

下面是一个简要的范例:

1 #include <stdlib.h> 

  2 #include <stdio.h> 

  3 

  4 int main(int argc,char** argv) 

  5 { 

  6     int x,y; 

  7     int arraysize; 

  8     int **array; 

  9 

 10     if(argc!=2) 

 11     { 

 12         printf("Usage: %s Enter arraysize value/n;",argv[0]); 

 13         exit(-1); 

 14     } 

 15     else 

 16     { 

 17         arraysize = atoi(argv[1]); 

 18         if(arraysize <=0) 

 19         { 

 20             printf("Array size must be larger than 0/n;"); 

 21             exit(-1); 

 22         } 

 23     } 

 24 

 25     array = (int**) malloc( arraysize*sizeof(int*)); 

 26 

 27     printf("Creating an %d by %d array /n",arraysize,arraysize); 

 28 

 29     if(array == NULL) 

 30     { 

 31         printf("Malloc failed for array size %d /n",arraysize); 

 32         exit(-1); 

 33     } 

 34 

 35     for (x=0;x<arraysize;x++) 

 36     { 

 37         array[x] = (int*) malloc (arraysize*sizeof(int)); 

 38 

 39         if(array[x] == NULL) 

 40         { 

 41             printf("Failed malloc for array size %d/n",arraysize); 

 42             exit(-1); 

 43         } 

 44     } 

 45 

 46     exit(0); 

 47 }



$ gcc -fprofile-arcs -ftest-coverage -g -o sample test.c 

$ ./sample 10 

Creating an 10 by 10 array 

 $ gcov test.c 

File '/usr/include/sys/sysmacros.h' 

Lines executed:0.00% of 6 

/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov' 


File 'test.c' 

Lines executed:57.89% of 19 

test.c:creating 'test.c.gcov' 

$ cat test.c.gcov 

        -:    0:Source:test.c 

        -:    0:Graph:test.gcno 

        -:    0:Data:test.gcda 

        -:    0:Runs:1 

        -:    0:Programs:1 

        -:    1:#include <stdlib.h> 

        -:    2:#include <stdio.h> 

        -:    3: 

        -:    4:int main(int argc,char** argv) 

        1:    5:{ 

        -:    6:        int x,y; 

        -:    7:        int arraysize; 

        -:    8:        int **array; 

        -:    9: 

        1:   10:        if(argc!=2) 

        -:   11:        { 

    #####:   12:                printf("Usage: %s Enter arraysize value;/n",argv[0]); 

    #####:   13:                exit(-1); 

        -:   14:        } 

        -:   15:        else 

        -:   16:        { 

        1:   17:                arraysize = atoi(argv[1]); 

        1:   18:                if(arraysize <=0) 

        -:   19:                { 

    #####:   20:                        printf("Array size must be larger than 0;/n"); 

    #####:   21:                        exit(-1); 

        -:   22:                } 

        -:   23:        } 

        -:   24: 

        1:   25:        array = (int**) malloc( arraysize*sizeof(int*)); 

        -:   26: 

        1:   27:        printf("Creating an %d by %d array /n",arraysize,arraysize); 

        -:   28: 

        1:   29:        if(array == NULL) 

        -:   30:        { 

    #####:   31:                printf("Malloc failed for array size %d /n",arraysize); 

    #####:   32:                exit(-1); 

        -:   33:        } 

        -:   34: 

       11:   35:        for (x=0;x<arraysize;x++) 

        -:   36:        { 

       10:   37:                array[x] = (int*) malloc (arraysize*sizeof(int)); 

        -:   38: 

       10:   39:                if(array[x] == NULL) 

        -:   40:                { 

    #####:   41:                        printf("Failed malloc for array size %d/n",arraysize); 

    #####:   42:                        exit(-1); 

        -:   43:                } 

        -:   44:        } 

        -:   45: 

        1:   46:        exit(0); 

        -:   47:}



正如我们看到的,我们现在的覆盖率是57.89%,#####后面的语句是现在没有够执行到的。

$ ./sample 

Usage: ./sample Enter arraysize value; 

$ gcov test.c 

File '/usr/include/sys/sysmacros.h' 

Lines executed:0.00% of 6 

/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov' 


File 'test.c' 

Lines executed:68.42% of 19 

test.c:creating 'test.c.gcov' 

现在运行了没有参数的情况,这种情况占了10.53%(68.42-57.89) 


$ ./sample 0 

Array size must be larger than 0; 

$ gcov test.c 

File '/usr/include/sys/sysmacros.h' 

Lines executed:0.00% of 6 

/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov' 


File 'test.c' 

Lines executed:78.95% of 19 

test.c:creating 'test.c.gcov'


有测试了一种情况,那就是arraysize=0现在的覆盖率已经达到了78.95%

还有两种情况,那就是二维数组的第一维和第二维内存空间的申请了。在现在的状况下,malloc()不太可能分配失败,所以我们借助工具gdb来模拟malloc失败时的情况。

$ gdb sample 

GNU gdb Red Hat Linux (6.6-16.fc7rh) 

Copyright (C) 2006 Free Software Foundation, Inc. 

GDB is free software, covered by the GNU General Public License, and you are 

welcome to change it and/or distribute copies of it under certain conditions. 

Type "show copying" to see the conditions. 

There is absolutely no warranty for GDB.  Type "show warranty" for details. 

This GDB was configured as "i386-redhat-linux-gnu"... 

Using host libthread_db library "/lib/libthread_db.so.1". 

(gdb) l

1       #include <stdlib.h> 

2       #include <stdio.h> 

3 

4       int main(int argc,char** argv) 

5       { 

6               int x,y; 

7               int arraysize; 

8               int **array; 

9 

10              if(argc!=2) 

(gdb) l 

11              { 

12                      printf("Usage: %s Enter arraysize value/n;",argv[0]); 

13                      exit(-1); 

14              } 

15              else 

16              { 

17                      arraysize = atoi(argv[1]); 

18                      if(arraysize <=0) 

19                      { 

20                              printf("Array size must be larger than 0/n;"); 

(gdb) l 

21                              exit(-1); 

22                      } 

23              } 

24 

25              array = (int**) malloc( arraysize*sizeof(int*)); 

26 

27              printf("Creating an %d by %d array /n",arraysize,arraysize); 

28 

29              if(array == NULL) 

30              {

(gdb) b 29 

Breakpoint 1 at 0x8048ada: file test.c, line 29. 

(gdb) r 10 

Starting program: /home/secularbird/Templates/test/sample 10 

Creating an 10 by 10 array 


Breakpoint 1, main (argc=2, argv=0xbf9b59f4) at test.c:29 

29              if(array == NULL) 

(gdb) print array 

$1 = (int **) 0x90af008 

(gdb) set array=0 

(gdb) print array 

$2 = (int **) 0x0 

(gdb) step 

31                      printf("Malloc failed for array size %d /n",arraysize); 

(gdb) cont 

Continuing. 

Malloc failed for array size 10 


Program exited with code 0377. 

(gdb) quit


l命令是list的简写,显示了程序的源代码。
b 29是在29行设置一个断点,之所以这样设置是因为array空间申请过了,我们为了模拟需要改变它的值。
r 10是run 10,也就是运行程序,10是传递的命令行参数。
print array打印array的地址
set array=0将array指向空地址
step一步一步的运行程序,简写是s
cont继续运行程序
quit退出gdb。

接下来做类似的工作产生另一个malloc fail。

$ gdb sample 

GNU gdb Red Hat Linux (6.6-16.fc7rh) 

Copyright (C) 2006 Free Software Foundation, Inc. 

GDB is free software, covered by the GNU General Public License, and you are 

welcome to change it and/or distribute copies of it under certain conditions. 

Type "show copying" to see the conditions. 

There is absolutely no warranty for GDB.  Type "show warranty" for details. 

This GDB was configured as "i386-redhat-linux-gnu"... 

Using host libthread_db library "/lib/libthread_db.so.1". 

(gdb) l

1       #include <stdlib.h> 

2       #include <stdio.h> 

3 

4       int main(int argc,char** argv) 

5       { 

6               int x,y; 

7               int arraysize; 

8               int **array; 

9 

10              if(argc!=2) 

(gdb) l 

11              { 

12                      printf("Usage: %s Enter arraysize value/n;",argv[0]); 

13                      exit(-1); 

14              } 

15              else 

16              { 

17                      arraysize = atoi(argv[1]); 

18                      if(arraysize <=0) 

19                      { 

20                              printf("Array size must be larger than 0/n;"); 

(gdb) l 

21                              exit(-1); 

22                      } 

23              } 

24 

25              array = (int**) malloc( arraysize*sizeof(int*)); 

26 

27              printf("Creating an %d by %d array /n",arraysize,arraysize); 

28 

29              if(array == NULL) 

30              { 

(gdb) l 

31                      printf("Malloc failed for array size %d /n",arraysize); 

32                      exit(-1); 

33              } 

34 

35              for (x=0;x<arraysize;x++) 

36              { 

37                      array[x] = (int*) malloc (arraysize*sizeof(int)); 

38 

39                      if(array[x] == NULL) 

40                      {

(gdb) b 39 

Breakpoint 1 at 0x8048b7a: file test.c, line 39. 

(gdb) r 10 

Starting program: /home/secularbird/Templates/test/sample 10 

Creating an 10 by 10 array 


Breakpoint 1, main (argc=2, argv=0xbfdcae04) at test.c:39 

39                      if(array[x] == NULL) 

(gdb) print array[0] 

$1 = (int *) 0x9bc2038 

(gdb) set array[0]=0 

(gdb) step 

41                              printf("Failed malloc for array size %d/n",arraysize); 

(gdb) cont 

Continuing. 

Failed malloc for array size 10 


Program exited with code 0377. 

(gdb) quit 


$ gcov test.c 

File '/usr/include/sys/sysmacros.h' 

Lines executed:0.00% of 6 

/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov' 


File 'test.c' 

Lines executed:100.00% of 19 

test.c:creating 'test.c.gcov'


到现在为止每条路径都测试过了,代码覆盖率也已经达到了100%,也可以查看一下test.c.gcov文件
里面已经没有#####了。

参考资料:
Linux® Debugging and Performance Tuning: Tips and Techniques By Steve Best
man gcov
http://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html#Gcov-Data-Files