本文整体内容参考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了