软件测试
单元测试
Gcov 是一个测试覆盖程序,集成在 GCC 中,随 GCC 一起发布。
基本概念
基本块 BB
基本块指一段顺序执行的程序代码:当其中第一条语句被执行后,块内每一条语句都会被执行一次,因此基本块内所有语句的执行次数相同。基本块一般由多个顺序执行的语句后接一个跳转语句组成。
跳转 ARC
从一个 BB 到另一个 BB 的跳转称为一个 ARC。要知道程序中每个语句和分支的执行次数,就必须知道每个 BB 和 ARC 的执行次数。
程序流图
如果把 BB 作为一个节点,一个函数中的所有 BB 就构成一个有向图。根据图论可知,有向图中 BB 的入度和出度相同,因此只要知道部分 BB 或 ARC 的执行次数,就可以推断出全部。这里选择由 ARC 的执行次数来推断 BB 的执行次数,所以只需对部分 ARC 插桩,只要能统计出所有 BB 和 ARC 的执行次数即可。
原理
测试程序首先进行编译预处理,生成汇编文件,并完成插桩。插桩时会在源文件末尾插入一个静态数组,数组的大小等于该源文件中桩点的个数,数组的值就是各桩点的执行次数;每个桩点插入 3~4 条汇编语句,直接写入生成的 *.s 文件。汇编文件经汇编生成目标文件,程序运行过程中由桩点负责收集执行信息。
使用
编译
测试代码 say.c 如下:
#include <stdio.h>
int say(char *what) {
printf("------ %s\n", what);
return 0;
}cmain.c 如下:
#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;
}c添加 -fprofile-arcs -ftest-coverage -fPIC 编译参数编译程序,生成可执行程序和 *.gcno 文件,其中记录了行信息和程序流图信息:
$ gcc -fprofile-arcs -ftest-coverage -fPIC -O0 say.c main.c
$ ls
a.out main.c main.gcno say.c say.gcnosh数据收集
运行可执行文件,会在相应 *.o 文件所在目录默认生成 *.gcda 文件,其中记录了 *.c 文件中的程序执行情况,包括跳变次数等:
$ ./a.out
------ bye
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcnosh报告生成
针对某个文件的执行情况,可以通过如下命令生成报告,并创建 *.gcov 文件:
$ gcov -a main.c
File 'main.c'
Lines executed:80.00% of 5
Creating 'main.c.gcov'sh常用选项如下,更多内容可参考 Invoking gcov:
-b:分支覆盖
-a:所有基本块覆盖
-f:函数覆盖plaintext注意事项
- 编译时不要添加优化选项,否则代码会发生变化,无法准确定位。
- 代码中复杂的宏(如宏展开后是循环或其他控制结构)可以用内联函数代替,因为
gcov只统计宏调用出现的那一行。 - 每行代码最好只包含一条语句。
*.gcno与*.gcda需要匹配,两个文件通过时间戳记录是否匹配。- 若编译动态库,链接时需要添加
-lgcov。
图形化展示
gcov 生成的报告分散在各个源码文件对应的 *.gcov 文件中,难以汇总分析,可视化效果也较差,因此需要转换成可视化的图形报告。lcov 和 gcovr 两个工具可以完成这项工作,两者功能基本相同。其中 gcovr 是一个用 Python 编写的开源软件,大小只有几十 KB。
列表形式
- 代码覆盖率
$ 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%
------------------------------------------------------------------------------sh报告显示程序运行后覆盖了 80% 的代码。
- 分支覆盖率
$ 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%
------------------------------------------------------------------------------sh报告显示 main.c 中有一个分支没有执行到。
HTML 文件形式
$ gcovr -r . --html -o xxx.html
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno xxx.htmlsh添加 --html 参数后即可生成 html 文件,用浏览器打开,如下图所示:

还可以添加 --html-details 选项,为每个代码文件单独生成 html 文件:
$ 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.htmlsh可以看到多了 xxx.main.c.html 和 xxx.say.c.html 两个文件,用浏览器打开 xxx.html,如下图所示:

文件名较之前带上了下划线,单击文件名即可查看具体的代码覆盖情况,如下图所示:

测试样例生成
当前主流的商用代码测试软件大都支持测试样例自动生成。这里调研了老牌的测试软件 Parasoft C/C++ test 与国产新推出的测试工具 wings。
| 对比方面 | wings | C/C++ test |
|---|---|---|
| 测试样例生成灵活性 | 可以根据需求,修改赋值次数 | 基于Google Test框架,一次性生成固定组值 |
| 测试类型 | 见下方表格 | 见下方表格 |
| 生成方式 | 基于xml的值填充 | 基于Google Test框架的测试样例代码 |
| 应用范围 | 国产替代,使用较少 | 市场占比极大 |
| 获取难易 | 使用依赖于厂家的数据库格式,需要厂家提供转换插件,获取难度大 | 有破解版,可用 |
