zoukankan      html  css  js  c++  java
  • 基于gtest、gmock、mockcpp和lcov的C语言LLT工程 —— LLT构造和lcov查看覆盖率实例

    由于工作原因,之前在CI这一块一直是依照公司流程走的,LLT这一块都是照猫画虎,对于整体框架自己没有一个完整的概念,最近有时间,研究了一下整体的逻辑框架,在此记录一下。

    关于gtest,gmock和mockcpp,这里不再细讲,知道gtest,gmock是google的一套用于C/C++ LLT的框架即可,要用到mockcpp是因为gmock其实只能用于对对象函数的mocker,不能对C代码中的一般函数进行mcoker,这个在后面的代码中可以看出来。

    一、准备工作

    1. googletest、googlemock

    下载:https://github.com/google/googletest

    2. mockcpp

    https://code.google.com/archive/p/mockcpp/downloads

    3. 编译mockcpp

    最新的2.6版本的mockcpp只需要解压然后进入根目录,然后:

    cmake .  //生成Makefile

    make install

     这里需要把libmockcpp.a保存下来,后面会用到;

    /usr/local/include/文件夹下面的所有文件也需要保存下来;

    4. 编译googletest

    直接在下载地根目录进行

    cmke .

    make

    即可得到gmock, gtest编译结果和静态库:

     保存libgtest.a和libgmock.a后面用于静态链接;

    二、建立测试工程

    1. 创建GtestLearn工程,这里是我们需要测试的代码,目录结构如下:

     mian.c

     1 #include <stdio.h>
     2 #include "func.h"
     3 
     4 int main(int argc, char **argv) {
     5     int ret = 0;
     6     struct test_t test;
     7 
     8     ret = add(1, 2);
     9     printf("Get add result: %d
    ", ret);
    10 
    11     test.a = 10;
    12     test.b = 12;
    13     ret = add_struct(&test);
    14     printf("Get add struct result: %d
    ", ret);
    15 
    16     test.p_func = NULL;
    17     ret = test_struct_func(&test);
    18     printf("Get test struct result: %d
    ", ret);
    19 
    20     ret = test_stub_func();
    21     printf("Get test stub func result: %d
    ", ret);
    22 
    23     return 0;
    24 }

    func.h

    #ifndef GTESTLEARN_FUNC_H
    #define GTESTLEARN_FUNC_H
    
    struct test_t {
        int a;
        int b;
        int (*p_func)(struct test_t *test);
    };
    
    int add(int a, int b);
    int multi(int a, int b);
    int add_struct(struct test_t *test);
    int test_struct_func(struct test_t *test);
    int test_stub_func();
    
    #endif //GTESTLEARN_FUNC_H

    func.c

    #include <stdio.h>
    #include "ex_func.h"
    #include "func.h"
    
    int add(int a, int b)
    {
        printf("start to compute the sum of a %d and b %d
    ", a, b);
        return a + b;
    }
    
    int multi(int a, int b)
    {
        printf("start to compute the multi of a %d and b %d
    ", a, b);
        return a * b;
    }
    
    int add_struct(struct test_t *test)
    {
        int sum;
        int multi_v;
    
        printf("start to compute the sum of a %d and b %d
    ", test->a, test->b);
    
        sum = test->a + test->b;
        multi_v = multi(test->a, test->b);
        if (sum > multi_v) {
            return sum;
        }
    
        return multi_v;
    }
    
    int test_struct_func(struct test_t *test)
    {
        if (test->p_func == NULL) {
            printf("get null func pointer
    ");
            return 0xFFFF;
        }
        printf("start ro run test_struct_func with a %d b %d
    ", test->a, test->b);
        return test->p_func(test);
    }
    
    int test_stub_func()
    {
        int ret;
        int a = 0;
    
        ret = ex_get_value(&a);
        if (ret == 0xFFFF) {
            printf("get extern value failed, ret %d
    ", ret);
            return ret;
        }
    
        printf("get extern value succeed, ex value %d
    ", a);
        return ret;
    }

    ex_func.h

    #ifndef GTESTLEARN_EX_FUNC_H
    #define GTESTLEARN_EX_FUNC_H
    int ex_get_value(int *a);
    #endif //GTESTLEARN_EX_FUNC_H

    ex_func.c

    #include "ex_func.h"
    
    int ex_get_value(int *a)
    {
        *a = 101010;
        return 0;
    }

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.15)
    project(GtestLearn C)

    set(CMAKE_C_STANDARD 99)
    set(CMAKE_C_FLAGS "${CAMKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
    include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/)
    set(SOURCE_FILES
    main.c
    include/func.h
    func.c
    include/ex_func.h
    include/ex_func.c)

    add_executable(GtestLearn ${SOURCE_FILES})
    target_link_libraries(GtestLearn ${LIBRARIES})

    这里保证通过

    cmake .

    make

    然后执行,预期正确。

    2. 创建测试工程GtestLearnLLT

    独立一个工程是因为在正式开发环境中,做LLT的代码往往是与业务代码分离的,更好的模拟真实使用环境。

    目录结构:

    这里将googlemock,googletest和mockcpp放在third_party文件夹,都是由之前下载的源码解压而来,其中:

    mockcpp只需要保留3rdparty文件夹,include文件用之前编译的头文件代替(准备工作第3步);lib目录里为之前编译产生的静态库;

    stubs文件夹保存的是桩函数;

    googlemock和googletest新建lib目录,存放之前编译的静态库;

    main.cpp

    #include <stdio.h>
    #include "gtest/gtest.h"
    
    GTEST_API_ int main(int argc, char **argv) {
        printf("Running main() from gtest_main.cc
    ");
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
    }

    gtest_ut.cpp

    extern "C" {
    #include <stdio.h>
    #include <stdlib.h>
    #include "func.h"
    }
    
    #include <limits.h>
    #include <mockcpp/mockcpp.hpp>
    #include "gmock/gmock.h"
    #include "gtest/gtest.h"
    
    // using namespace std;
    using namespace testing;
    
    class GtestUt : public testing::Test
    {
    protected:
        void SetUp() override
        {
            std::cout << "--Gtest_Ut SetUP--" << std::endl;
        }
    
        void TearDown() override
        {
            std::cout << "--Gtest_Ut TearDown--" << std::endl;
        }
    };
    
    class Mock_FOO {
    public:
        MOCK_METHOD1(mock_test_struct_func, int(struct test_t *test));
    };
    
    Mock_FOO mocker;
    
    int mock_test_struct_func(struct test_t *test)
    {
        return mocker.mock_test_struct_func(test);
    }
    
    TEST_F(GtestUt, ut_add_01)
    {
        int ret;
    
        ret = add(1, 2);
        EXPECT_EQ(3, ret);
    }
    
    TEST_F(GtestUt, ut_add_02)
    {
        int ret;
        struct test_t test;
    
        test.a = 1;
        test.b = 1;
    
        MOCKER(multi)
        .expects(atMost(20))
        .will(returnValue(100));
        ret = add_struct(&test);
        EXPECT_EQ(ret, 100);
        GlobalMockObject::verify();
    }
    
    TEST_F(GtestUt, ut_add_03)
    {
        int ret;
        struct test_t test;
    
        test.a = 10;
        test.b = 11;
    
        MOCKER(multi)
        .expects(atMost(20))
        .will(returnValue(20));
        ret = add_struct(&test);
        EXPECT_EQ(ret, 21);
        GlobalMockObject::verify();
    }
    
    TEST_F(GtestUt, ut_add_04)
    {
        int ret;
        int a, b;
        struct test_t test;
    
        test.a = 10;
        test.b = 11;
        test.p_func = mock_test_struct_func;
        EXPECT_CALL(mocker, mock_test_struct_func(&test)).WillRepeatedly(Return(10));
    
        ret = test_struct_func(&test);
        EXPECT_EQ(ret, 10);
        GlobalMockObject::verify();
    }
    
    TEST_F(GtestUt, ut_add_05)
    {
        int ret;
        int ex_value;
    
        ret = test_stub_func();
        EXPECT_EQ(ret, 1011);
    }

    my_stubs.c

    #include <stdio.h>
    #include "ex_func.h"
    
    int ex_get_value(int *a)
    {
        if (a == NULL) {
            printf("get null pointer %p
    ", a);
            return 0xFFFF;
        }
        *a = 1011;
        printf("run stub func, get value %d
    ", *a);
        return *a;
    }

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.15)
    project(GtestLearnLLT)

    set(CMAKE_CXX_STANDARD 14)
    set(CMAKE_CXX_FLAGS "${CAMKE_CXX_FLAGS} -std=c++11 -pthread -fprofile-arcs -ftest-coverage")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DCONFIG_LLT -fprofile-arcs -ftest-coverage")
    set(THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/third_party)

    link_directories(${THIRD_PARTY_PATH}/mockcpp/lib
    ${THIRD_PARTY_PATH}/googlemock/lib
    ${THIRD_PARTY_PATH}/googletest/lib)

    #${THIRD_PARTY_PATH}/googletest/
    include_directories(${THIRD_PARTY_PATH}/googletest/include/
    ${THIRD_PARTY_PATH}/googlemock/include/
    ${THIRD_PARTY_PATH}/mockcpp/include/
    ${THIRD_PARTY_PATH}/mockcpp/3rdparty/
    ${THIRD_PARTY_PATH}/googlemock/
    ${CMAKE_CURRENT_SOURCE_DIR}/../GtestLearn/include/
    ${CMAKE_CURRENT_SOURCE_DIR}/stubs/)

    #${THIRD_PARTY_PATH}/googletest/src/gtest-all.cc
    set(SRC_FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/../GtestLearn/func.c
    ${CMAKE_CURRENT_SOURCE_DIR}/../GtestLearn/main.c
    ${CMAKE_CURRENT_SOURCE_DIR}/stubs/my_stubs.c
    ${THIRD_PARTY_PATH}/googlemock/src/gmock-all.cc
    gtest_ut.cpp
    main.cpp)

    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")

    add_executable(GtestLearnLLT ${SRC_FILES})
    target_link_libraries(GtestLearnLLT libmockcpp.a libgmock.a libgtest.a)

    编译执行:

    cmake .

    make

    执行结果:

     注意:这里还是在编译的时候编译了gmock而不是直接链接,是因为直接使用静态库,会导致mock对象泄露的问题,目前没有定位到原因:

     三、代码分析

    1. 不需要打桩的普通函数LLT:

    TEST_F(GtestUt, ut_add_01)
    {
        int ret;
    
        ret = add(1, 2);
        EXPECT_EQ(3, ret);
    }

    这个比较简单,没有什么说的,依靠gtest框架,检查函数功能的正确性;

    2. 使用mockcpp来mocker一般C函数:

    TEST_F(GtestUt, ut_add_02)
    {
        int ret;
        struct test_t test;
    
        test.a = 1;
        test.b = 1;
    
        MOCKER(multi)
        .expects(atMost(20))
        .will(returnValue(100));
        ret = add_struct(&test);
        EXPECT_EQ(ret, 100);
        GlobalMockObject::verify();
    }

    使用mockcpp提供的MOCKER来对需要验证的目标函数add_struct中的multi来进行mocker,进行打桩,常用来覆盖不同的异常分支。

    3. 使用自定义桩函数

    int ex_get_value(int *a)
    {
        if (a == NULL) {
            printf("get null pointer %p
    ", a);
            return 0xFFFF;
        }
        *a = 1011;
        printf("run stub func, get value %d
    ", *a);
        return *a;
    }
    TEST_F(GtestUt, ut_add_05)
    {
        int ret;
        int ex_value;
    
        ret = test_stub_func();
        EXPECT_EQ(ret, 1011);
    }

    对test_stub_func里面调用的ex_get_value来进行打桩,来实现自己想要完成的代码逻辑;

    4. 使用gmock来打桩对象类函数,本例中的对象类函数是放在结构体test_t中的:

    struct test_t {
        int a;
        int b;
        int (*p_func)(struct test_t *test);
    };

    使用gmock打桩:

    class Mock_FOO {
    public:
        MOCK_METHOD1(mock_test_struct_func, int(struct test_t *test));
    };
    
    Mock_FOO mocker;
    
    int mock_test_struct_func(struct test_t *test)
    {
        return mocker.mock_test_struct_func(test);
    }

    用Mock_FOO类的mock_test_struct_func函数来mocker结构体test_t的成员函数p_func:

    TEST_F(GtestUt, ut_add_04)
    {
        int ret;
        int a, b;
        struct test_t test;
    
        test.a = 10;
        test.b = 11;
        test.p_func = mock_test_struct_func;
        EXPECT_CALL(mocker, mock_test_struct_func(&test)).WillRepeatedly(Return(10));
    
        ret = test_struct_func(&test);
        EXPECT_EQ(ret, 10);
        GlobalMockObject::verify();
    }

    代码中 test.p_func = mock_test_struct_func; 使用Mock_FOO的函数来替代原结构体中的成员函数,通过 EXPECT_CALL  在test_struct_func函数调用结构体成员函数p_func的时候使用Mock_FOO的 mock_test_struct_func 来代替,达到打桩的目的

    四、使用Lcov来查看LLT覆盖率

    1、lcov下载与安装

    wget http://downloads.sourceforge.net/ltp/lcov-1.9.tar.gz

    tar -zxvf lcov-1.9.tar.gz

    cd lcov-1.9

    make install

    2、LLT工程CMakeLists.txt修改

    添加 -fprofile-arcs -ftest-coverage 为lcov的工作生成.gcda .gcno文件

    set(CMAKE_CXX_FLAGS "${CAMKE_CXX_FLAGS} -std=c++11 -pthread -fprofile-arcs -ftest-coverage")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DCONFIG_LLT -fprofile-arcs -ftest-coverage")

    3、lcov需要代码运行之后才能生成.gcda和.gcno文件

    编译运行LLT工程之后可以看到生成的文件:

    可以看到我们需要进行LLT的代码main.c和func.c的gcda和gcno文件再CMakeFiles文件下;

    4、使用lcov获取代码被执行次数的信息:

    lcov -d ./CMakeFiles/GtestLearnLLT.dir/home/xxxx/GtestLearn/ -t bin/GtestLearnLLT -o test.info -b . -c

    -d  指向你需要进行统计覆盖率代码所在的目录

    -t  为测试工程可执行文件所在目录

    -o 生成的输出文件

    -b 相对路径,之前的路径基于此路径

    -c 获取覆盖率信息

     5、使用genhtml生成网页

    genhtml -o result/ test.info

    -o 指向生成的html信息存放的目录

    test.info是之前生成的包含覆盖率信息的文件

    6、查看生成的目录

    7、使用浏览器打开index.html

    点开一个文件查看LLT覆盖情况:

    遇到的问题:

    *.gcda:stamp mismatch with notes file

    解决方法:

    当.gcda文件比.gcno文件更新时,您可能会收到“戳记不匹配”.

    使用 hexdump -e '"%x "' -s8 -n4 main.c.gcda 查看时间戳,会发现gcda文件和gcno文件时间戳不一致;

    它主要有两个原因:
    1.您可能在运行测试之后和跟踪文件生成之前重新构建代码.
    2.二进制文件可以在一台机器上构建,测试在其他机器上运行,其时间早于构建机器.

    我的解决方法:

    rm -rf CMakeFiles/    彻底删除这个文件夹,删除所有.gcda和 .gcno文件

    然后重新编译和执行LLT工程就可以了

  • 相关阅读:
    ORM和JDBC
    四种会话跟踪技术以及jstl介绍
    GC、进程和线程的定义
    数组和链表的理解,及各自的优缺点
    JSP和Servlet及浏览器与tomcat交互过程
    多线程、同步实现方法及Error和Exception的区别与联系
    Eclipse创建一个普通maven项目详细步骤
    Eclipse创建一个动态maven项目详细步骤
    Myeclipse项目出现红叉解决方案
    数据结构和算法 — 平衡二叉树的实现
  • 原文地址:https://www.cnblogs.com/heimianshusheng/p/13530672.html
Copyright © 2011-2022 走看看