在软件开发过程中,为了验证代码块功能的完备及健壮性,我们常常会做一些单元测试,验证函数的调用符合预期,再加上gcov、lcov这样的工具,可以生成HTML格式的单元测试结果,极大地提供了可读性。下面以一个动态库的单元测试为例,简述gcov、lcov的用法。
1、代码准备
在“/home/demo”目录下,包括测试代码test.c和被测试代码demo.h、demo.c,如下:
// demo.h
#ifndef DEMO_H
#define DEMO_H
void demo_foo();
void demo_foo2(int i);
#endif /* DEMO_H */
// demo.c
#include <stdio.h>
#include "demo.h"
void demo_foo()
{
printf("%s\n", __func__);
}
void demo_foo2(int i)
{
if (i > 0) {
printf("%s pos\n", __func__);
if (0 == i % 2) {
printf("%s even\n", __func__);
}
else {
printf("%s odd\n", __func__);
}
}
else if (i < 0) {
printf("%s neg\n", __func__);
}
else {
printf("%s zero\n", __func__);
}
}
// test.c
#include <stdio.h>
#include "demo.h"
int main(int argc, char* argv[])
{
printf("%s unit test begin\n", __func__);
demo_foo();
demo_foo2(2);
demo_foo2(1);
demo_foo2(0);
demo_foo2(-1);
printf("%s unit test end\n", __func__);
return 0;
}
demo.c中定义了两个函数,test.c中分别调用了这两个函数进行测试,其中demo_foo2()函数传入了不同的参数以满足100%条件覆盖,这是一个很简单的例子。
2、生成可执行文件
我们的例子测试的是一个动态库,所以先把demo.h/demo.c编译成一个动态库libdemo.so:
gcc -shared -fpic -o libdemo.so demo.h demo.c
然后编译test.c并链接libdemo.so为可执行文件test:
gcc -o test test.c -L. -ldemo
这一步操作生成了libdemo.so和test两个文件:
我们先来运行一下test这个可执行文件,看它能否正确执行。
LD_LIBRARY_PATH=. ./test
main unit test begin
demo_foo
demo_foo2 pos
demo_foo2 even
demo_foo2 pos
demo_foo2 odd
demo_foo2 zero
demo_foo2 neg
main unit test end
从上面的log可以看出,我们的可执行文件test是没有问题的。
3、重新编译动态库
对一个动态库进行单体测试,为了能够生成HTML形式的测试结果,我们使用gcc编译时,还需要添加两个编译参数,“-fprofile-arcs”和“-ftest-coverage”,前者用来生成对应的”.gcda”后缀的数据,后者用来生成对应的”.gcno”后缀的数据,这两种数据是必须的。因为我们要测试的是动态库,所以需要重新编译这个动态库,而没有必要重新编译其它文件,这一点需要注意。
gcc -fprofile-arcs -ftest-coverage -shared -fpic -o libdemo.so demo.h demo.c
这一步重新编译了libdemo.so,并且产生了对应的demo.gcno数据:
4、运行可执行文件
LD_LIBRARY_PATH=. ./test
main unit test begin
demo_foo
demo_foo2 pos
demo_foo2 even
demo_foo2 pos
demo_foo2 odd
demo_foo2 zero
demo_foo2 neg
main unit test end
这一步生成了demo.gcda数据:
5、使用gcov生成.gcov数据
命令:gcov demo.c
生成文件:demo.c.gcov
我们来看看demo.c.gcov文件有什么内容:
cat demo.c.gcov
-: 0:Source:demo.c
-: 0:Graph:demo.gcno
-: 0:Data:demo.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:#include "demo.h"
-: 3:
1: 4:void demo_foo()
-: 5:{
1: 6: printf("%s\n", __func__);
1: 7:}
-: 8:
4: 9:void demo_foo2(int i)
-: 10:{
4: 11: if (i > 0) {
2: 12: printf("%s pos\n", __func__);
2: 13: if (0 == i % 2) {
1: 14: printf("%s even\n", __func__);
-: 15: }
-: 16: else {
1: 17: printf("%s odd\n", __func__);
-: 18: }
-: 19: }
2: 20: else if (i < 0) {
1: 21: printf("%s neg\n", __func__);
-: 22: }
-: 23: else {
1: 24: printf("%s zero\n", __func__);
-: 25: }
4: 26:}
6、使用lcov生成.info数据
lcov -c -d . -o demo.info
Capturing coverage data from .
Found gcov version: 4.8.4
Scanning . for .gcda files ...
Found 1 data files in .
Processing demo.gcda
Finished .info-file creation
-c用来抓取覆盖率数据,-d指定覆盖率数据所在的目录,-o指定生成的目标文件,后缀为.info。这一步生成了demo.info,不过demo.c.gcov文件被自动清理了。
7、最后一步
使用genhtml命令生成html。
genhtml demo.info -o out
Reading data file demo.info
Found 1 entries.
Found common filename prefix "/home"
Writing .css and .png files.
Generating output.
Processing file demo/demo.c
Writing directory view page.
Overall coverage rate:
lines......: 100.0% (13 of 13 lines)
functions..: 100.0% (2 of 2 functions)
branches...: 100.0% (6 of 6 branches)
-o指定html文件存放位置,这里我们把它放置在当前目录的out文件夹中:
打开out目录下的index.html查看结果如下:
点击“demo”目录:
点击“demo.c”文件: