使用
gcov
进
行保
险测试
简
介
在
这
章里,我
们
将会探
讨
gcov
实
用程序,并且了解一下如何使用
gcov
来帮助
测试
与支持
软
件配置与
优
化。我
们
将会了解如何使用
gcov
来构建
软
件,并且理解他所
提供的各
种
数据
类
型。最后,我
们
将探
讨
当
执
行保
险测试时
要避免的事情。
gcov
是什
么
?
我
们
从
gcov
可以
为
我
们
做什
么开
始。
gcov
是一个保
险测试
工具。当构建一个程序
时
,
gcov
会
监视
一个程序的
执
行,并且会
标识
出
执
行了哪一行源
码
,哪一行没有
执
行。
更
进
一
步
,
gcov
可以
标识
出某一行源
执
行的次数,
这对
于
执
行配置很有用
(程序在哪里花
费
了大多数的
时间
)。因
为
gcov
可以分辨出哪一行没有
执
行,
这对
于保
险测试
工具是很有用的。
我
们
将会
讨论
3.2.2
版本的
GNU
编译
器工具
链
的
gcov
的用法。
gcov
使用目的
使用该工具可以查看测试的覆盖率,进而分析测试用例设计的缺陷。
使用该工具可以查看程序在某分支处的执行频率,进而分析程序的性能。
使用
gcov
注意事项
该工具每次重新编译后,统计数据会被清空。所以项目测试必须在确定的版本上
进行测试,而版本有修正后应使用
gcov
工具对变更部分进行补充测试。
准
备镜
像
让
我
们
来看一下如何
为
gcov
的使用准
备镜
像。我
们
将会在接下来的部分提供更
为
详细
的
gcov
的
选项
,所以
这
里只是作
为
一个介
绍
。我
们
将将会使用下面的所列的
bubblesort
的源
码
:
1: #include <stdio.h>
2:
3: void bubbleSort( int list[], int size )
4: {
5: int i, j, temp, swap = 1;
6:
7: while (swap) {
8:
9: swap = 0;
10:
11: for ( i = (size-1) ; i >= 0 ; i— ) {
12:
13: for ( j = 1 ; j <= i ; j++ ) {
14:
15: if ( list[j-1] > list[j] ) {
16:
17: temp = list[j-1];
18: list[j-1] = list[j];
19: list[j] = temp;
20: swap = 1;
21:
22: }
23:
24: }
25:
26: }
27:
28: }
29:
30: }
31:
32: int main()
33: {
34: int theList[10]={10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
35: int i;
36:
37: /* Invoke the bubble sort algorithm */
38: bubbleSort( theList, 10 );
39:
40: /* Print out the final list */
41: for (i = 0 ; i < 10 ; i++) {
42: printf("%d/n", theList[i]);
43: }
44:
45: }
gcov
程序将会与
编译
器工具
链
一起使用。
这
就意味着我
们
将要在其上
进
行保
险测试
的
镜
像必
须
用一个特殊的
选项
集合
进
行
编译
。下面是我
们
用来演示
编译
bubbleSort.c
的命令:
gcc bubblesort.c -o bubblesort -ftest-coverage -fprofile-arcs
当我
们执
行生成的程序
时
会生成一些包含
关
于程序的相
关
数据的文件。
gcov
程序将会
使用
这
些文件来
报
告数据并且向
开发
者提供相
应
的信息。当指定
-ftest-coverage
选项时
会
为每
一个源
码
生成两个文件。
这
些文件会使用
.bb
与
.bbg
作
为扩
展名,并且用
这
些
文件来重
组每
一个可
执
行程序的程序流
图
。
对
于
-fprofile-arcs
,将会生成一个包含
每
一个指令分支的
执
行
计
数的以
.da
为扩
展名的文件。
这
些文件会在
执
行以后与
源
码
文件一起使用,来
标识
源
码
的
执
行行
为
。
使用
gcov
程序
现
在我
们
准
备
好了我
们
的程序
镜
像了,
让
我
们继续
我
们
其余的部分。运行我
们
的程序就
会生成我
们
在前面所
讨论
的数据集文件。然后我
们
使用我
们
希望
进
行
检测
的源
码
运行
gcov
程序。如下面所示:
$ ./bubblesort
...
$ gcov bubblesort.c
100.00% of 17 source lines executed in file bubblesort.c
Creating bubblesort.c.gcov.
这
告
诉
我
们
在我
们
的例子程序中所有的源
码
行至少都
执
行了一次。我
们
可以通
过查
看
所生成的
bubblesort.c.gcov
文件来了解
每
一源
码
行所
实际
运行的次数。如下面所示:
-: 0:Source:bubblesort.c
-: 0:Graph:bubblesort.gcno
-: 0:Data:bubblesort.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:void bubbleSort(int list[],int size)
1: 3:{
1: 4: int i,j,temp,swap=1;
4: 5: while(swap)
-: 6: {
2: 7: swap=0;
22: 8: for(i=(size-1);i>=0;i--)
-: 9: {
110: 10: for(j=1;j<=i;j++)
-: 11: {
90: 12: if(list[j-1]>list[j])
-: 13: {
45: 14: temp=list[j-1];
45: 15: list[j-1]=list[j];
45: 16: list[j]=temp;
45: 17: swap=1;
-: 18: }
-: 19: }
-: 20: }
-: 21: }
1: 22:}
-: 23:int main()
1: 24:{
1: 25: int theList[10]={10,9,8,7,6,5,4,3,2,1};
-: 26: int i;
-: 27: /*Invoke the buble sort algorithm*/
1: 28: bubbleSort(theList,10);
-: 29:
-: 30: /*print out the final list*/
11: 31: for(i=0;i<10;i++)
-: 32: {
10: 33: printf("%d/n",theList[i]);
-: 34: }
1: 35: return 0;
-: 36:}
现
在
让
我
们
来看一下其中的一些
关键
点,看一下他所提供的内容。第一列
显
示了源
码
中
每
一行源
码
所
执
行的次数。在一些情况下,
执
行次数并没有提供。
这
些只是并不会
影响代
码
的
简单
C
源
码
元素。
这
些
计
数可以提供一些
关
于程序
执
行的信息。例如,
测试
的第
12
行
执
行了
90
次,
而
14-17
行的代
码
只是
执
行了
45
次。
这
告
诉
我
们
当
这
个函数
调
用了
90
次,
真正成功的
仅
是
45
次。
换
句
话说
,大部分的
测试时间
浪
费
在两个元素的交
换
上。
这
是由于
测试
数据的
顺
序所造成的。
从
这
里我
们
可以看到代
码
段中最常
执
行的部分就是排序算法的内循
环
部分。
这
是因
为
由于退出
测试
第
10
行要比第
12
行
执
行的次数多一些。
查
看分支概率
我
们
也可以使用
-b
选项
来
查
看程序的分支数据。
这
个
选项
会
输
出程序中
每
一个
分支的
频
度与相
应
的摘要。例如,我
们
使用
-b
选项
来
执
行
gcov
命令:
$ gcov -b bubblesort.c
100.00% of 17 source lines executed in file bubblesort.c
100.00% of 12 branches executed in file bubblesort.c
100.00% of 12 branches taken at least once in file bubblesort.c
100.00% of 2 calls executed in file bubblesort.c
Creating bubblesort.c.gcov.
所生成的
bubblesort.c.gcov
文件如下所示。
-: 0:Source:bubblesort.c
-: 0:Graph:bubblesort.gcno
-: 0:Data:bubblesort.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:void bubbleSort(int list[],int size)
function bubbleSort called 1 returned 100% blocks executed 100%
1: 3:{
1: 4: int i,j,temp,swap=1;
4: 5: while(swap)
branch 0 taken 67%
branch 1 taken 33% (fallthrough)
-: 6: {
2: 7: swap=0;
22: 8: for(i=(size-1);i>=0;i--)
branch 0 taken 91%
branch 1 taken 9% (fallthrough)
-: 9: {
110: 10: for(j=1;j<=i;j++)
branch 0 taken 82%
branch 1 taken 18% (fallthrough)
-: 11: {
90: 12: if(list[j-1]>list[j])
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 13: {
45: 14: temp=list[j-1];
45: 15: list[j-1]=list[j];
45: 16: list[j]=temp;
45: 17: swap=1;
-: 18: }
-: 19: }
-: 20: }
-: 21: }
1: 22:}
-: 23:int main()
function main called 1 returned 100% blocks executed 100%
1: 24:{
1: 25: int theList[10]={10,9,8,7,6,5,4,3,2,1};
-: 26: int i;
-: 27: /*Invoke the buble sort algorithm*/
1: 28: bubbleSort(theList,10);
call 0 returned 100%
-: 29:
-: 30: /*print out the final list*/
11: 31: for(i=0;i<10;i++)
branch 0 taken 91%
branch 1 taken 9% (fallthrough)
-: 32: {
10: 33: printf("%d/n",theList[i]);
call 0 returned 100%
-: 34: }
1: 35: return 0;
-: 36:}
从
这
里我
们
可看到
这
与上面的文件相
类
似,但是
这
一次
每
一个分支点都用他
们
的
频
度
进
行了
标
示。
分支点依
赖
于目
标结
构建指令集。第
12
行是一个
简单
的
if
语
句,所以有一个分支点。
在
这
里我
们
可以注意到
这
是
50%
,
这
通
过
我
们
前面
观
察程序的
执
行次数可以看出。
其他的分支点有一些
难
于分析。例如,第
7
行是一个
while
语
句,有两个分支点。
在
X86
汇编
中,
这
一行分
编译
成我
们
下面所看到的
样
子:
1: cmpl $0, -20(%ebp)
2: jne .L4
3: jmp .L1
从
这
里我
们
可看出,
swap
变
量与
0
进
行比
较
。如果他不等于
0,
就会跳
转
到第
2
行,
.L4
。否
则
要跳
转
到第
3
行
,.L1
。第
2
行所示的分支概率
为
67%
。
这
是因
为这
一行
执
行
3
次,
但是
jne
只
执
行了两次。当第
2
行的
jne
并没有
执
行
时
,我
们
直
势头
跳
转
到第
3
行。
这
只
执
行一次,但是一旦
执
行,程序就
结
束了。所以分支
1
要花
费
100%
的
时间
。
所以分支概率在理解程序流
时
是要相当有用的,但是要参考
汇编
需要理解分支点在哪里。
不完整程序
测试
当
gcov
计
数一个
测试
并不是
100%
的程序
时
,并没有
执
行的行是
标记为
####
,
而不是
执
行次数。
下面
显
示的是一个由
gcov
创
建的文件来
显
示少于
100%
的
测试
。
1: #include <stdio.h>
2:
3: int main()
4: 1 {
5: 1 int a=1, b=2;
6:
7: 1 if (a == 1) {
8: 1 printf("a = 1/n");
9: } else {
10: ###### printf("a != 1/n");
11: }
12:
13: 1 if (b == 1) {
14: ###### printf("b = 1/n");
15: } else {
16: 1 printf("b != 1/n");
17: }
18:
19: 1 return 0;
20: }
当
这
个程序运行
时
,
gcov
实
用程序也会向
标
准
输
出
输
出相
应
的信息。他会
显
示可能
执
行的源
码
行的行数以及
实际
运行的百分比。
$ gcov incomptest.c
77.78% of 9 source lines executed in file incomptest.c
Creating incomptest.c.gcov.
$
如果我
们
的例子程序有多个函数,我
们
可以通
过
使用
-f
选项
来
查
看
每
一个函数
的
执
行情况。
如下面的我
们
以
bubbleSort
程序所
进
行的演示:
$ gcov -f bubblesort.c
100.00% of 11 source lines executed in function bubbleSort
100.00% of 6 source lines executed in function main
100.00% of 17 source lines executed in file bubblesort.c
Creating bubblesort.c.gcov.
$
gcov
可用的
选项
gcov
程序
调
用的格式
为
:
gcov [options] sourcefile
其可用的
选项
如下:
选项
目的
-v
,
-version
打印版本信息
-h,-help
打印帮助信息
-b,-branch-probabilities
向
输
出文件
输
出分支
频
度
-c,-branch-counts
打印分支
计
数而不是分支
频
度
-n,-no-output
不
创
建
gcov
输
出文件
-l,-long-file-names
创
建
长
文件名
-f,-function-summaries
打印
每
一个函数的概要
-o,-object-directory .bb,.bbg,.da
文件存放的目
录
从上面
这
个表中,我
们
可以看到一个
单
个字符
选项
,以及一个
长选项
。当从命令行
中使用
gcov
命令
时
短
选项
是比
较
有用的,但是当
gcov
是
Makefile
的一个部分
时
,
应
使用
长选项
,因
为这
更易于理解。
当了解
gcov
程序的版本信息
时
,可以使用
-v
选项
。因
为
gcov
是与一个指定的
编译
器
工具
链联
系在一起的(
实际
上是由
gcc
工具
链
而构建的),
gcc
版本与
gcov
的版本是相同的。
gcov
程序的
简
介以及
选项
帮助可以用
-h
选项
来
进
行
显
示