使用
gcov
进
行保
险测试
简
介
在 这 章里,我 们 将会探 讨 gcov 实 用程序,并且了解一下如何使用 gcov 来帮助 测试
在 这 章里,我 们 将会探 讨 gcov 实 用程序,并且了解一下如何使用 gcov 来帮助 测试
与支持
软
件配置与
优
化。我
们
将会了解如何使用
gcov
来构建
软
件,并且理解他所
提供的各
种
数据
类
型。最后,我
们
将探
讨
当
执
行保
险测试时
要避免的事情。
gcov 是什 么 ?
我 们 从 gcov 可以 为 我 们 做什 么开 始。 gcov 是一个保 险测试 工具。当构建一个程序 时 ,
gcov 是什 么 ?
我 们 从 gcov 可以 为 我 们 做什 么开 始。 gcov 是一个保 险测试 工具。当构建一个程序 时 ,
gcov
会
监视
一个程序的
执
行,并且会
标识
出
执
行了哪一行源
码
,哪一行没有
执
行。
更
进
一
步
,
gcov
可以
标识
出某一行源
执
行的次数,
这对
于
执
行配置很有用
(程序在哪里花
费
了大多数的
时间
)。因
为
gcov
可以分辨出哪一行没有
执
行,
这对
于保
险测试
工具是很有用的。
我 们 将会 讨论 3.2.2 版本的 GNU 编译 器工具 链 的 gcov 的用法。
我 们 将会 讨论 3.2.2 版本的 GNU 编译 器工具 链 的 gcov 的用法。
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 程序将会与 编译 器工具 链 一起使用。 这 就意味着我 们 将要在其上 进 行保 险测试 的
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 程序将会
gcc bubblesort.c -o bubblesort -ftest-coverage -fprofile-arcs
当我 们执 行生成的程序 时 会生成一些包含 关 于程序的相 关 数据的文件。 gcov 程序将会
使用
这
些文件来
报
告数据并且向
开发
者提供相
应
的信息。当指定
-ftest-coverage
选项时
会
为每
一个源
码
生成两个文件。
这
些文件会使用
.bb
与
.bbg
作
为扩
展名,并且用
这
些
文件来重
组每
一个可
执
行程序的程序流
图
。
对
于
-fprofile-arcs
,将会生成一个包含
每
一个指令分支的
执
行
计
数的以
.da
为扩
展名的文件。
这
些文件会在
执
行以后与
源
码
文件一起使用,来
标识
源
码
的
执
行行
为
。
使用 gcov 程序
现 在我 们 准 备 好了我 们 的程序 镜 像了, 让 我 们继续 我 们 其余的部分。运行我 们 的程序就
使用 gcov 程序
现 在我 们 准 备 好了我 们 的程序 镜 像了, 让 我 们继续 我 们 其余的部分。运行我 们 的程序就
会生成我
们
在前面所
讨论
的数据集文件。然后我
们
使用我
们
希望
进
行
检测
的源
码
运行
gcov
程序。如下面所示:
$ ./bubblesort
...
$ gcov bubblesort.c
100.00% of 17 source lines executed in file bubblesort.c
Creating bubblesort.c.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:}
现 在 让 我 们 来看一下其中的一些 关键 点,看一下他所提供的内容。第一列 显 示了源 码
-: 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 次,
这 些 计 数可以提供一些 关 于程序 执 行的信息。例如, 测试 的第 12 行 执 行了 90 次,
而
14-17
行的代
码
只是
执
行了
45
次。
这
告
诉
我
们
当
这
个函数
调
用了
90
次,
真正成功的
仅
是
45
次。
换
句
话说
,大部分的
测试时间
浪
费
在两个元素的交
换
上。
这
是由于
测试
数据的
顺
序所造成的。
从 这 里我 们 可以看到代 码 段中最常 执 行的部分就是排序算法的内循 环 部分。
从 这 里我 们 可以看到代 码 段中最常 执 行的部分就是排序算法的内循 环 部分。
这
是因
为
由于退出
测试
第
10
行要比第
12
行
执
行的次数多一些。
查 看分支概率
我 们 也可以使用 -b 选项 来 查 看程序的分支数据。 这 个 选项 会 输 出程序中 每 一个
查 看分支概率
我 们 也可以使用 -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:}
从 这 里我 们 可看到 这 与上面的文件相 类 似,但是 这 一次 每 一个分支点都用他 们 的
$ 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 语 句,所以有一个分支点。
分支点依 赖 于目 标结 构建指令集。第 12 行是一个 简单 的 if 语 句,所以有一个分支点。
在
这
里我
们
可以注意到
这
是
50%
,
这
通
过
我
们
前面
观
察程序的
执
行次数可以看出。
其他的分支点有一些
难
于分析。例如,第
7
行是一个
while
语
句,有两个分支点。
在
X86
汇编
中,
这
一行分
编译
成我
们
下面所看到的
样
子:
1: cmpl $0, -20(%ebp)
2: jne .L4
3: jmp .L1
从 这 里我 们 可看出, swap 变 量与 0 进 行比 较 。如果他不等于 0, 就会跳 转 到第 2 行,
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% 的程序 时 ,并没有 执 行的行是 标记为 #### ,
而不是
执
行次数。
下面
显
示的是一个由
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 实 用程序也会向 标 准 输 出 输 出相 应 的信息。他会 显 示可能
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 选项 来 查 看 每 一个函数
$ 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 -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 是与一个指定的 编译 器
当了解 gcov 程序的版本信息 时 ,可以使用 -v 选项 。因 为 gcov 是与一个指定的 编译 器
工具
链联
系在一起的(
实际
上是由
gcc
工具
链
而构建的),
gcc
版本与
gcov
的版本是相同的。
gcov 程序的 简 介以及 选项 帮助可以用 -h 选项 来 进 行 显 示
gcov 程序的 简 介以及 选项 帮助可以用 -h 选项 来 进 行 显 示