基于gtest/gmock/mockcpp的单元测试探索

本文整体内容参考https://www.cnblogs.com/heimianshusheng/p/13530672.html(后面统一称为"引文"),在实际调试中发现了一些问题并予以解决,记录一下方便自己和同道中人查阅。

目的

  • 通过实例演练学习使用gtest对C语言编写的程序进行单元测试
  • 学习如何用mockcpp对C语言的函数进行mock

准备工作

版本信息

admin@osu-2:~$ cat /etc/issue
Ubuntu 20.04 LTS \n \l

admin@osu-2:~$ 
admin@osu-2:~$ gcc -v
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) 
admin@osu-2:~$ 
admin@osu-2:~$ g++ -v
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) 
admin@osu-2:~$ 
admin@osu-2:~$ cmake --version
cmake version 3.16.3

安装gtest

*GitHub上的googletest有很多release的版本。最新的版本对编译器的版本1.12要求较高( 不低于C++14),所以选取v1.8.1

admin@osu-2:~$ mkdir try_gtest
admin@osu-2:~$ cd try_gtest/
admin@osu-2:~/try_gtest$ 
admin@osu-2:~/try_gtest$ git clone https://github.com/google/googletest 
admin@osu-2:~/try_gtest$ cd googletest/
admin@osu-2:~/try_gtest/googletest$ git checkout release-1.8.1
admin@osu-2:~/try_gtest/googletest$ git branch
* (HEAD detached at release-1.8.1)
  main
admin@osu-2:~/try_gtest/googletest$ 
  • 编译
admin@osu-2:~/try_gtest/googletest$ mkdir build
admin@osu-2:~/try_gtest/googletest$ cd build/
admin@osu-2:~/try_gtest/googletest/build$ cmake ..
admin@osu-2:~/try_gtest/googletest/build$ make
  • 确认编出了四个.a文件
admin@osu-2:~/try_gtest/googletest/build$ find . -name "*.a"
./googlemock/libgmock.a
./googlemock/libgmock_main.a
./googlemock/gtest/libgtest_main.a
./googlemock/gtest/libgtest.a
admin@osu-2:~/try_gtest/googletest/build$ 
  • 直接安装了,省的后面编译的时候还要指定这些.a文件的路径
admin@osu-2:~/try_gtest/googletest/build$ sudo make install

安装mockcpp

  • 下载后解压缩
admin@osu-2:~/try_gtest$ wget https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/mockcpp/mockcpp-2.6.tar.gz
admin@osu-2:~/try_gtest$ tar -zxvf mockcpp-2.6.tar.gz 
  • 为了编译成功,需要修改mockcpp.h,新加include typeinfo,注释掉template 相关内容
admin@osu-2:~/try_gtest/mockcpp$ cp include/mockcpp/mockcpp.h  include/mockcpp/mockcpp.h_bak 
admin@osu-2:~/try_gtest/mockcpp$ 
admin@osu-2:~/try_gtest/mockcpp$ vim  include/mockcpp/mockcpp.h  
admin@osu-2:~/try_gtest/mockcpp$ 
admin@osu-2:~/try_gtest/mockcpp$ 
admin@osu-2:~/try_gtest/mockcpp$ diff -ruN include/mockcpp/mockcpp.h_bak include/mockcpp/mockcpp.h
--- include/mockcpp/mockcpp.h_bak       2023-01-14 15:31:04.076954592 +0800
+++ include/mockcpp/mockcpp.h   2023-01-14 15:31:47.389499701 +0800
@@ -19,6 +19,7 @@
 #ifndef __MOCKCPP_H
 #define __MOCKCPP_H
 
+#include <typeinfo>
 
 #if !defined(MOCKCPP_NO_NAMESPACE) || (MOCKCPP_NO_NAMESPACE == 0)
 # define MOCKCPP_NS mockcpp
@@ -55,11 +56,11 @@
 
 #endif
 
-template <bool condition>
-struct static_assert
-{
-    typedef int static_assert_failure[condition ? 1 : -1];
-};
+//template <bool condition>
+//struct static_assert
+//{
+//    typedef int static_assert_failure[condition ? 1 : -1];
+//};
 
 
 #endif // __MOCKCPP_H
admin@osu-2:~/try_gtest/mockcpp$ 
  • 指定从mockcpp/src能够找到googoletest/include的路径
admin@osu-2:~/try_gtest/mockcpp/src$ ls -l ../../googletest/googletest/include/
total 4
drwxrwxr-x 3 admin admin 4096 1月  14 15:00 gtest
admin@osu-2:~/try_gtest/mockcpp/src$ xunit_home='../../googletest/googletest'
admin@osu-2:~/try_gtest/mockcpp/src$ cd ..
admin@osu-2:~/try_gtest/mockcpp$ 
  • 带参数执行cmake
 admin@osu-2:~/try_gtest/mockcpp$ cmake -DMOCKCPP_XUNIT=gtest -DMOCKCPP_XUNIT_HOME=$xunit_home ./
  • 如果make如下报错
 admin@osu-2:~/try_gtest/mockcpp$ make 
[  1%] Generating ../include/mockcpp/DelegatedMethodGetDef.h
Traceback (most recent call last):
  File "/home/admin/try_gtest/mockcpp/src/generate_vtbl_related_files.py", line 5, in <module>
    from get_long_opt import *
  File "/home/admin/try_gtest/mockcpp/src/get_long_opt.py", line 29
    print sys.argv[0], getUsageString(longOpts)
          ^
SyntaxError: invalid syntax
make[2]: *** [src/CMakeFiles/vtbl_related_headers.dir/build.make:65: include/mockcpp/DelegatedMethodGetDef.h] Error 1
make[1]: *** [CMakeFiles/Makefile2:123: src/CMakeFiles/vtbl_related_headers.dir/all] Error 2
make: *** [Makefile:130: all] Error 2
admin@osu-2:~/try_gtest/mockcpp$ 
  • 可以看到报错的python print是按照python2的格式写的,Ubuntu20默认没有Python2,那就安装一下
admin@osu-2:~/try_gtest$ sudo apt install python -y

admin@osu-2:~/try_gtest$ sudo rm -f /usr/bin/python
admin@osu-2:~/try_gtest$ sudo ln -s /usr/bin/python2 /usr/bin/python
  • 删除 CMakeCache.txt后重新cmake
admin@osu-2:~/try_gtest/mockcpp$ rm CMakeCache.txt 
admin@osu-2:~/try_gtest/mockcpp$ 
admin@osu-2:~/try_gtest/mockcpp$ 
admin@osu-2:~/try_gtest/mockcpp$ cmake -DMOCKCPP_XUNIT=gtest -DMOCKCPP_XUNIT_HOME=$xunit_home ./
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonInterp: /usr/bin/python (found version "2.7.18") 
-- Configuring done
-- Generating done
-- Build files have been written to: /home/admin/try_gtest/mockcpp
admin@osu-2:~/try_gtest/mockcpp$ 
  • 编译应该就不会报错了,再安装一下
admin@osu-2:~/try_gtest/mockcpp$ make

admin@osu-2:~/try_gtest/mockcpp$ sudo make install

需要测试的C代码

  • 目录结构
admin@osu-2:~/try_gtest/GtestLearn$ tree
.
├── CMakeLists.txt
├── func.c
├── include
│   ├── ex_func.c
│   ├── ex_func.h
│   └── func.h
└── main.c

1 directory, 5 files
admin@osu-2:~/try_gtest/GtestLearn$ 
  • 代码内容(CMakeLists.txt的内容对比引文有删减)
admin@osu-2:~/try_gtest/GtestLearn$ cat 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})
admin@osu-2:~/try_gtest/GtestLearn$ 
admin@osu-2:~/try_gtest/GtestLearn$ 
============================================
admin@osu-2:~/try_gtest/GtestLearn$ cat 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\n", a, b);
    return a + b;
}

int multi(int a, int b)
{
    printf("start to compute the multi of a %d and b %d\n", 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\n", 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\n");
        return 0xFFFF;
    }
    printf("start to run test_struct_func with a %d b %d\n", 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\n", ret);
        return ret;
    }

    printf("get extern value succeed, ex value %d\n", a);
    return ret;
}
admin@osu-2:~/try_gtest/GtestLearn$ 
admin@osu-2:~/try_gtest/GtestLearn$ 
============================================
admin@osu-2:~/try_gtest/GtestLearn$ cat main.c 
#include <stdio.h>
#include "func.h"

int main(int argc, char **argv) {
    int ret = 0;
    struct test_t test;

    ret = add(1, 2);
    printf("Get add result: %d\n", ret);

    test.a = 10;
    test.b = 12;
    ret = add_struct(&test);
    printf("Get add struct result: %d\n", ret);

    test.p_func = NULL;
    ret = test_struct_func(&test);
    printf("Get test struct result: %d\n", ret);

    ret = test_stub_func();
    printf("Get test stub func result: %d\n", ret);

    return 0;
}
admin@osu-2:~/try_gtest/GtestLearn$ 
============================================
admin@osu-2:~/try_gtest/GtestLearn$ cat include/ex_func.h
#ifndef GTESTLEARN_EX_FUNC_H
#define GTESTLEARN_EX_FUNC_H
int ex_get_value(int *a);
#endif //GTESTLEARN_EX_FUNC_H
admin@osu-2:~/try_gtest/GtestLearn$ 
============================================
admin@osu-2:~/try_gtest/GtestLearn$ cat include/ex_func.c
#include "ex_func.h"

int ex_get_value(int *a)
{
    *a = 101010;
    return 0;
}
admin@osu-2:~/try_gtest/GtestLearn$ 
============================================
admin@osu-2:~/try_gtest/GtestLearn$ cat include/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
admin@osu-2:~/try_gtest/GtestLearn$ 
  • 编译并执行编译出的可执行文件
admin@osu-2:~/try_gtest/GtestLearn$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/admin/try_gtest/GtestLearn
admin@osu-2:~/try_gtest/GtestLearn$ 
admin@osu-2:~/try_gtest/GtestLearn$ 
admin@osu-2:~/try_gtest/GtestLearn$ make
Scanning dependencies of target GtestLearn
[ 25%] Building C object CMakeFiles/GtestLearn.dir/main.c.o
[ 50%] Building C object CMakeFiles/GtestLearn.dir/func.c.o
[ 75%] Building C object CMakeFiles/GtestLearn.dir/include/ex_func.c.o
[100%] Linking C executable GtestLearn
[100%] Built target GtestLearn
admin@osu-2:~/try_gtest/GtestLearn$ 
admin@osu-2:~/try_gtest/GtestLearn$ ./GtestLearn 
start to compute the sum of a 1 and b 2
Get add result: 3
start to compute the sum of a 10 and b 12
start to compute the multi of a 10 and b 12
Get add struct result: 120
get null func pointer
Get test struct result: 65535
get extern value succeed, ex value 101010
Get test stub func result: 0
admin@osu-2:~/try_gtest/GtestLearn$ 

单元测试

  • 针对上面的GtestLearn项目编写gtest/gmock测试用例并且编译执行(被测代码是C,测试用例是C++)
  • 目录架构
admin@osu-2:~/try_gtest$ cd GtestLearnLLT
admin@osu-2:~/try_gtest/GtestLearnLLT$ tree
.
├── CMakeLists.txt
├── gtest_ut.cpp
├── main.cpp
└── stubs
    └── my_stubs.c

1 directory, 4 files
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
  • 文本内容如下
admin@osu-2:~/try_gtest/GtestLearnLLT$ cat 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")

include_directories(
        ${CMAKE_CURRENT_SOURCE_DIR}/../GtestLearn/include/
        ${CMAKE_CURRENT_SOURCE_DIR}/stubs/)

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
        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)
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
============================================
admin@osu-2:~/try_gtest/GtestLearnLLT$ cat 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 = 5;
    test.b = 5;

    MOCKER(multi)
    .expects(atMost(20))
    .will(returnValue(8));
    ret = add_struct(&test);
    EXPECT_EQ(ret, 10);
    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);
}
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
============================================
admin@osu-2:~/try_gtest/GtestLearnLLT$ cat main.cpp
#include <stdio.h>
#include "gtest/gtest.h"

GTEST_API_ int main(int argc, char **argv) {
    printf("Running main() from gtest_main.cc\n");
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
============================================
admin@osu-2:~/try_gtest/GtestLearnLLT$ cat stubs/my_stubs.c 
#include <stdio.h>
#include "ex_func.h"

int ex_get_value(int *a)
{
    if (a == NULL) {
        printf("get null pointer %p\n", a);
        return 0xFFFF;
    }
    *a = 1011;
    printf("run stub func, get value %d\n", *a);
    return *a;
}
admin@osu-2:~/try_gtest/GtestLearnLLT$ 
  • 标准编译流程
admin@osu-2:~/try_gtest/GtestLearnLLT$ mkdir build
admin@osu-2:~/try_gtest/GtestLearnLLT$ cd build/
admin@osu-2:~/try_gtest/GtestLearnLLT/build$ cmake ..
admin@osu-2:~/try_gtest/GtestLearnLLT/build$ make
  • 执行编出的可执行文件(先忽略最后的报错了)
admin@osu-2:~/try_gtest/GtestLearnLLT/build$ ../bin/GtestLearnLLT 
Running main() from gtest_main.cc
[==========] Running 5 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 5 tests from GtestUt
[ RUN      ] GtestUt.ut_add_01
--Gtest_Ut SetUP--
start to compute the sum of a 1 and b 2
--Gtest_Ut TearDown--
[       OK ] GtestUt.ut_add_01 (0 ms)
[ RUN      ] GtestUt.ut_add_02
--Gtest_Ut SetUP--
start to compute the sum of a 1 and b 1
--Gtest_Ut TearDown--
[       OK ] GtestUt.ut_add_02 (0 ms)
[ RUN      ] GtestUt.ut_add_03
--Gtest_Ut SetUP--
start to compute the sum of a 10 and b 11
--Gtest_Ut TearDown--
[       OK ] GtestUt.ut_add_03 (0 ms)
[ RUN      ] GtestUt.ut_add_04
--Gtest_Ut SetUP--
start to run test_struct_func with a 10 b 11
--Gtest_Ut TearDown--
[       OK ] GtestUt.ut_add_04 (0 ms)
[ RUN      ] GtestUt.ut_add_05
--Gtest_Ut SetUP--
run stub func, get value 1011
get extern value succeed, ex value 1011
--Gtest_Ut TearDown--
[       OK ] GtestUt.ut_add_05 (0 ms)
[----------] 5 tests from GtestUt (0 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 1 test case ran. (0 ms total)
[  PASSED  ] 5 tests.

/home/admin/try_gtest/GtestLearnLLT/gtest_ut.cpp:90: ERROR: this mock object (used in test GtestUt.ut_add_04) should be deleted but never is. Its address is @0x5583cb6f00c0.
ERROR: 1 leaked mock object found at program exit. Expectations on a mock object is verified when the object is destructed. Leaking a mock means that its expectations aren't verified, which is usually a test bug. If you really intend to leak a mock, you can suppress this error using testing::Mock::AllowLeak(mock_object), or you may use a fake or stub instead of a mock.
admin@osu-2:~/try_gtest/GtestLearnLLT/build$ 

测试用例分析

ut_add_01(不需要mock的普通函数)

  • 测试步骤
TEST_F(GtestUt, ut_add_01)
{
    int ret;

    ret = add(1, 2);
    EXPECT_EQ(3, ret);
}
  • 不需要mock,就是验证func.c中的add函数,通过gtest的EXPECT_EQ来断言结果应该是3

ut_add_02(使用mockcpp来mock一般C函数)

  • 测试步骤
TEST_F(GtestUt, ut_add_02)
{
    int ret;
    struct test_t test;

    test.a = 5;
    test.b = 5;

    MOCKER(multi)
    .expects(atMost(20))
    .will(returnValue(8));
    ret = add_struct(&test);
    EXPECT_EQ(ret, 10);
    GlobalMockObject::verify();
}
  • 如果不mock
    • multi函数本来是做两个输入值的乘法5*5=25,
    • add_struct是比较两个数的和与积,然后返回和与积中较大的,5+5=10<25
    • 因此add_struct应该返回25
  • 通过mock
    • 将multi(5,5)的值人为替换为8,结果8<10
    • 这样add_struct的返回值就不是25,而应该是10了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值