利用gtest实现代码测试并统计白盒覆盖率
目标
1、利用gtest,搭建测试框架
2、利用lcov得到函数和分支的白盒覆盖率,利用genhtml输出覆盖率报告。
一、先编写功能代码,编译生成so
1、先编写功能代码
src/func.c
#include<stdio.h>
#include "func.h"
int add(int a, int b)
{
return a + b;
}
int del(int a, int b)
{
return a - b;
}
include/func.h (错误哦)
#ifndef __FUNC_H__
#define __FUNC_H__
int add(int a, int b);
int del(int a, int b);
#endif
注意:C和C++混合编程,c头文件中增加extern “C”,否则c++不能用c头文件。
include/func.h (正确哦)
#ifndef __FUNC_H__
#define __FUNC_H__
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b);
int del(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
2、编译自己的功能代码,生成.o,然后生成.a静态库
生成libfunc.a
root@linux:/home/code/3.test/2.gtest_exec/src# gcc -c func.c -I ../include -o ../lib/func.o
root@linux:/home/code/3.test/2.gtest_exec/src# ar rcs ../lib/libfunc.a ../lib/func.o
生成so在lib目录下
root@linux:/home/code/3.test/2.gtest_exec/src# gcc -shared -fPIC -I ../include func.c -o ../lib/libfunc.so
root@linux:/home/code/3.test/2.gtest_exec/src# ll
total 16
drwxr-xr-x 2 root root 4096 Jun 25 15:46 ./
drwxr-xr-x 5 root root 4096 Jun 25 15:41 ../
-rw-r--r-- 1 root root 126 Jun 25 15:46 func.c
二、编写测试代码,编译生成可执行文件
1、先下载gtest仓, 我们需要用到gtest的so和头文件
下载的gtest源码在/home/code/3.test/1.gtest/googletest/
我们下载源码是为了编译生成gtest的库文件,给我们的测试例使用。
2、编译gtest,生成静态库libgtest.a
cd googletest/bulid/
cmake ..
make
3、编写简单的测试用例
test/test.cpp
#include "gtest/gtest.h"
#include <iostream>
#include <string>
#include "func.h"
using namespace std;
// The fixture for testing class Foo.
class FooTest : public testing::Test
{
protected:
// You can remove any or all of the following functions if their bodies would
// be empty.
FooTest()
{
// You can do set-up work for each test here.
cout << "FootTest()" << endl;
}
~FooTest() override
{
// You can do clean-up work that doesn't throw exceptions here.
cout << "FooTest()" << endl;
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
void SetUp() override
{
// Code here will be called immediately after the constructor (right
// before each test).
cout << "SetUp()" << endl;
}
void TearDown() override
{
// Code here will be called immediately after each test (right
// before the destructor).
cout << "TearDown()" << endl;
}
// Class members declared here can be used by all tests in the test suite
// for Foo.
};
TEST_F(FooTest, test_add_func)
{
EXPECT_EQ(2, add(1, 1));
EXPECT_EQ(5, add(-1, 6));
EXPECT_EQ(-2, add(-1, -1));
cout << "test_add_func end" << endl;
}
TEST_F(FooTest, test_del_func)
{
EXPECT_EQ(0, del(1, 1));
EXPECT_EQ(-7, del(-1, 6));
cout << "test_del_func end" << endl;
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
4、编译cpp,生成可执行文件
将依赖的libgtest.a,拷贝到本工程的lib目录下
方式一:依赖libfunc.a静态库
root@linux:/home/code/3.test/2.gtest_exec/test# g++ test.cpp -o test -I /home/code/3.test/1.gtest/googletest/googletest/include/ -I ../include ../lib/libgtest.a ../lib/libfunc.a -lpthread
方式二:依赖libfunc.so动态库
root@linux:/home/code/3.test/2.gtest_exec/test# g++ test.cpp -o test -I /home/code/3.test/1.gtest/googletest/googletest/include/ -I ../include ../lib/libgtest.a -lpthread -lfunc -L../lib -Xlinker -rpath=../lib
注意:
加-lpthread,是因为gtest依赖它
加-I …/include,是因为测试例所依赖的功能代码的头文件在include目录
加-Xlinker -rpath=…/lib, 是为了解决下述问题
root@linux:/home/code/3.test/2.gtest_exec/test# g++ test.cpp -o test -I /home/code/3.test/1.gtest/googletest/googletest/include/ -I ../include ../lib/libgtest.a -lpthread -lfunc -L../lib
root@linux:/home/code/3.test/2.gtest_exec/test# ./test
./test: error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory
5、运行测试例(执行可执行文件)
./test
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from FooTest
[ RUN ] FooTest.test_add_func
FootTest()
SetUp()
test_add_func end
TearDown()
FooTest()
[ OK ] FooTest.test_add_func (0 ms)
[ RUN ] FooTest.test_del_func
FootTest()
SetUp()
test_del_func end
TearDown()
FooTest()
[ OK ] FooTest.test_del_func (0 ms)
[----------] 2 tests from FooTest (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.
三、借助于gcov和lcov统计代码覆盖率
gcov
gcov是gcc的一个组件,在编译时添加-ftest-coverage(编译后产生gcno文件,包含程序执行流图,代码块对应的行号信息)和-fprofile-arcs(运行后产生gcda文件,记录程序中基本块和arc跳转次数,进而得到每个语句和分支的执行次数)。
lcov,genhtml
lcov读取gcda和gcno,输出可读性更强的info文件,genhtml读取info文件,输出html覆盖率报告
1、源文件编译时,加入编译参数 -fprofile-arcs -ftest-coverage,生成源码.gcno
-
-ftest-coverage:在编译的时候产生.gcno文件,它包含了重建基本块图和相应的块的源码的行号的信息。
-
-fprofile-arcs:在运行编译过的程序的时候,会产生.gcda文件,它包含了弧跳变的次数等信息。
root@linux:/home/code/3.test/2.gtest_exec/src# gcc -shared -fPIC -fprofile-arcs -ftest-coverage -I ../include func.c -o ../lib/libfunc.so
为啥源文件也需要编译时带gcov参数?
因为后期用lcov生成覆盖率时,需要源文件的gcno文件和gcda文件,gcno文件必须要通过gcc编译时携带gcov参数才可以生成。
如果源码编译时未携带gcov参数,会在执行lcov时遇到如下问题:
报:缺少源文件的gcno。
geninfo: WARNING: GCOV did not produce any data for /home/code/3.test/2.gtest_exec/src/func.gcda
2、编译测试例,加入参数-fprofile-arcs -ftest-coverage,生成test.gcno
g++ test.cpp -o test -fprofile-arcs -ftest-coverage -I /home/code/3.test/1.gtest/googletest/googletest/include/ -I ../inc
lude ../lib/libgtest.a -lpthread -lfunc -L../lib
# ll
-rwxr-xr-x 1 root root 1181240 Jun 28 14:23 test*
-rw-r--r-- 1 root root 1581 Jun 25 15:54 test.cpp
-rw-r--r-- 1 root root 72404 Jun 28 14:23 test.gcno
3、执行测试例可执行文件,生成gcda(test.gcda和func.gcda)
代码运行才会生成gcda文件。
./test
测试例执行时,也会调用到源码函数,所以也会运行源码,当然也会生成源码的gcda文件func.gcda。
后续我们将用生成的gcda文件,通过lcov命令扫描所有gcda、gcno文件,生成函数和分支覆盖率信息。
其实主要是统计源码的覆盖率,所以必须要生成源码的gcda。
ll
-rw-r--r-- 1 root root 1581 Jun 25 15:54 test.cpp
-rw-r--r-- 1 root root 6736 Jun 28 14:25 test.gcda
-rw-r--r-- 1 root root 72404 Jun 28 14:25 test.gcno
find -name *.gcda
./src/func.gcda
./test/test.gcda
4、使用lcov,根据.gcno和.gcda文件生成覆盖率信息
生成gcda文件后,执行lcov命令,lcov会扫描工程下所有gcda文件,并且有gcda文件它会找对应的gcno文件,然后通过对比,生成函数\分支覆盖率信息。
lcov -c -d ../ -o test_gcov.info
注意:
-
-d: 表示统计覆盖率的代码路径,我在exec/test目录下执行命令,但是想统计exec整个工程的,需要-d到当前目录上一级目录。
-
-o: 生成的gcov信息文件
-
-c:
root@linux:/home/code/3.test/2.gtest_exec/test# lcov -c -d ../ -o test_gcov.info
Capturing coverage data from ../
Found gcov version: 9.4.0
Using intermediate gcov format
Scanning ../ for .gcda files ...
Found 1 data files in ../
Processing test.gcda
Finished .info-file creation
5、根据genhtml命令,生成可视化的lcov代码覆盖率报告
lcov – code coverage report
genhtml -o genhtml.info test_gcov.info
index.html文件
点击连接可以查看具体的函数分支覆盖情况
代码仓库
https://github.com/LisaPig/gtest
参考:
https://www.cnblogs.com/jlh-/p/16985906.html
编译报错的案例
https://blog.csdn.net/Charmve/article/details/126387362
gcov实践
https://blog.csdn.net/qq_32534441/article/details/90645316
https://blog.csdn.net/wz764110621/article/details/137351187