利用gtest实现代码测试并统计白盒覆盖率


利用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值