Linux项目搭建Gtest/Gmock框架总结

1. 前言

近期组里项目代码出于后期测试和长期维护的考虑,需要集成测试框架和用例补全,现有开源测试框架里面,google的test框架对于C++项目的支持度较高,且官方文档的完善性也高,便于上手,所以选用gtest这一套。

2. gtest环境搭建

下载地址:

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

环境搭建:

Git下来的源码里面包含了googletest和googlemock的文件夹,如果是做简单的gtest学习可以直接cmake编译就可以直接使用。如果需要加入到自己的C++项目中,需要编写Cmake文件,生成gtest/gmock静态库,再将编写的测试用例链接库就可以使用。我当前使用的目录结构如下图:
工程根目录
其中src为项目的源码文件夹,test文件夹用于存放测试用例,thirdpart用于存放三方库(gtest源码放在这里面)用于编译链接使用。
从github上下载的源码包含很多可选文件及文件夹,我本人使用的gtest文件目录主要包含这些文件夹,省去了没有用到的部分
在这里插入图片描述
在这里插入图片描述
剩下的就是Cmake文件的编写,写了个简单的demo如下:

project(myproject)
cmake_minimum_required(VERSION 3.0)

# 配置gcc编译参数
set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-O0 -m64 -g -fPIC ${CMAKE_CXX_FLAGS}")

set(GOOGLETEST_VERSION 1.10.0)
set(LIBRARY_OUTPUT_PATH lib)

#定义一些编译宏
add_definitions(-DFOR_GTEST)

#编译选项开关
option(BUILD_GMOCK "Builds the googlemock subproject" ON)
option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON)

# 头文件包含的目录
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googlemock/include
    ${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googlemock/src
    ${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googletest/include
    ${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googletest/src
)

# 需要包含的编译子目录
if(BUILD_GMOCK)
  add_subdirectory( thirdpart/gtest/googlemock )
else()
  add_subdirectory( thirdpart/gtest/googletest )
endif()

add_subdirectory( thirdpart/csv-parse)

# 编译单元测试用例代码
add_executable(unittest
    test/main/gmock_main.cc
    test/unit_test/xxx-test.cc
)
target_link_libraries(unittest
    -pthread
    gtest
    gmock
)

gtest运行参数

  1. 命令行参数:–gtest_output=“xml:report.xml”,可以把控制台内容输出转储为 XML 格式,其中report为存储的文件名,e.g.
./unittest --gtest_output="xml:report.xml"
  1. 命令行参数:–gtest_repeat=n,表示进行n次的重复测试,某些测试进行多次尝试后可能会结果不一致,可以使用重复测试功能进行测试;参数–gtest_break_on_failure可以支持自动调试,运行测试有时候会失败,但是在大多数时候会顺利通过。这是与内存损坏相关的问题的典型特点。如果多次运行测试,就能够提高发现失败的可能性,e.g.
./unittest --gtest_repeat=2 --gtest_break_on_failure

表示重复运行相同的测试用例两次,如果测试失败,会自动调用调试器。

  1. 命令行参数:运用–gtest_filter来过滤case。有时候我们并不需要运行所有测试,尤其是在修改的代码只影响某几个模块的情况下。为了支持运行一部分测试,Google 提供 --gtest_filter=,e.g.
./unittest --gtest_filter=* #执行所有测试
./unittest --gtest_filter=PoissonUdpClientTest* #执行PoissonUdpClientTest开头的测试
./unittest --gtest_filter=-PoissonUdpClientTest* #不执行PoissonUdpClientTest开头的测试
  1. 测试用例参数:利用DISABLED 关键词实现临时禁用测试
    说明:只需在逻辑测试名或单元测试名前面加上 DISABLE_ 前缀,它就不会执行了,e.g.
TEST(DISABLE_FooTest, DoSomething)
  1. 命令行参数:–gtest_also_run_disabled_tests,对于DISABLE的用例依然执行,e.g.
./unittest –gtest_also_run_disabled_tests

3. 编写测试用例

TEST使用

常规的简单测试可以使用TEST宏,具体形式如下:

// TestName表示本次测试的名称,第二个参数TestDecription用作描述,执行时控制台显示 TestName.TestDescription可以看到用例执行结果
TEST(TestName, TestDescription) 
{
    …
}

宏展开后的实质内容为TEST测试类:

class TestName_ TestDescription_Test : public testing::Test {
...
void TestBody()
{
    …
}
...
};

单元测试套件和TEST_F的使用

在单元测试中,我们经常需要在某个测试套件、测试用例或者整个测试运行之前进行前置条件设置及检查,或者运行之后对运行结果进行校验等操作,包括初始化操作和资源回收等,谷歌提供了这样一套测试套件,下面举例说明。

class FooTest : public ::testing::Test {
protected:
    //Override this to define how to set up the environment.
    virtual void SetUp() 
    {}
    // Override this to define how to tear down the environment.
    virtual void TearDown()
    {}
    // shared resource initialize
    static void SetUpTestCase()
    {}
    // shared resource delete
    static void TearDownTestCase()
    {}
};

测试套件类经常需要使用到的接口主要是以上4个,需要继承::testing::Test类,重写SetUp()TearDown()两个虚方法,在每个测试套件用例执行前会执行SetUp()的部分,在每个测试套件用例执行完之后会执行TearDown()的部分,主要用于每个执行用例的前置初始化和环境清除。
在测试套件的第一个测试用例开始前,SetUpTestCase函数会被调用,而在测试套件中的最后一个测试用例运行结束后,TearDownTestCase函数会被调用。适用场景主要是:
1)初始化数据涉及内存申请等操作,为每个测试实例构造对象将带来较大系统开销;
2)存在某数据,其在每个实例中均被用到,但每个实例都不会更改该数据的值。

有了上面的测试套件,接下来就只需写TEST_F测试用例了,形式参考

// 第一个参数必须为测试套件类名,第二个参数为测试描述
TEST_F(FooTest, FuncA)
{
    Foo foo;
    foo.FuncA();
}

值参数化和TEST_P使用

在测试用例中,我们时常需要传给被测函数不同的值,gtest为我们提供了简便的方法,可以使我们能够灵活的进行参数化测试。
这里直接给出最通用化场景值参数化的使用步骤:

  1. 将需要使用的多参数封装成一个参数类,如
//Step1:申明一个呼叫参数类,该类主要用于TEST_P宏中实现的测试逻辑使用
// 把需要用到两个参数封装成一个参数类
class CallArgs {
public:
  CallArgs(int argOne, double argTwo) :
    m_argOne(argOne), m_argTwo(argTwo) {}

  int GetArgOne()
  {
  	return m_argOne;
  }
  double GetArgTwo()
  {
  	return m_argTwo;
  }

private:
  int m_argOne;
  double m_argTwo;
};
  1. 申明一个呼叫类,该类类名同时也是TEST_P宏的第一个参数test_case_name
// 该类继承了TestWithParam<CallArgs>模版类,从而使得CallArgs类与Call类进行了关联。
class Call: public ::testing::TestWithParam<CallArgs> {
};
  1. 使用INSTANTIATE_TEST_CASE_P宏,对Call类进行类相关多个的参数设置
// 这里只添加了两组参数,事实上,可以添加更多。
// 这里使用参数生成器::testing::Values,GTest定义了了很多参数生成器。
INSTANTIATE_TEST_CASE_P(VOIP, Call, ::testing::Values(
    CallArgs(2, 9.6),
    CallArgs(4, 14.5)
    ) );
  1. 编写了使用TEST_P宏实现的测试用例
// 使用了TestWithParam<CallArgs>类的GetParam()接口获取参数CallArgs
// 实际上这是两个测试用例,即该代码段会执行两组用例
TEST_P( Call, makeCall)
{
  CallArgs args = GetParam();
  ASSERT_TRUE(makeCall(args.GetArgOne(), args.GetArgTwo()));
}

protected/private方法测试

测试类的public方法,测试配置的输入一般为入参和成员变量的状态值,测试的输出一般为返回值和某些成员状态变化的值,但是类对象不能直接访问protected/private成员。
如果需要测试类的protected/private方法,我这里总结了3种方法:

  1. 首先一般不建议对非public成员函数直接访问,如果需要测试非public成员函数,可以被测试的类中新建一些public接口进行调用的方式;

  2. 可以在被测类的头文件中加入类似下面这种宏定义,将protected/private成员转为public访问类型

#ifdef FOR_GTEST
    #define private public
    #define protected public
#endif

然后在cmake文件中加入FOR_GTEST的编译宏,在测试代码中就可以直接访问调用

add_definitions(-DFOR_GTEST)
  1. 使用gtest框架的提供的FRIEND_TEST
    在被测类的头文件需要引入包含该宏的头文件gtest/gtest_prod.h,然后添加宏进行友元声明,简单示例如下:
// foo.h
#include <gtest/gtest_prod.h>

// Defines FRIEND_TEST.
class Foo {
  ...
 private:
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  int Bar(void* x);
};

// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
  Foo foo;
  EXPECT_EQ(0, foo.Bar(NULL));
  // Uses Foo's private member Bar().
}

gmock使用

gmock是谷歌推出的开源白盒测试工具,用于编写C++模拟类的框架。通过gmock可以用一些简单的宏描述想要模拟的接口并指定其期望,在测试中有效地去除外部依赖,更方便地测试模块功能。
对类里面需要打桩的函数mock,语法如下:

MOCK_METHODn(..., ...); //其中n表示参数的个数
MOCK_CONST_METHODn(..., ...); //const成员方法用这种

对mock的方法可以指定期望,包括返回值,调用次数等,使用EXPECT_CALL()宏:

EXPECT_CALL(mock_object, method(matchers))
    .Times(cardinality) //可以指定调用几次
    .WillOnce(action) //可以指定调用行为
    .WillRepeatedly(action);

Matchers指参数匹配器,可以指定任意参数,::testing::_ 表示输入的参数为任意参数,其他参数不一一列举

Mock virtual方法如下,不需要对工程代码做修改:

// Foo.h
class Foo {
public:
    virtual int FooFuncOne(int num);
};

// FooTest.cc
class MockFoo : public Foo {
public:
    MOCK_METHOD1(FooFuncOne, int(int num));
};

TEST(FooTest, FooFuncOne)
{
    MockFoo mockFoo;
    // 指定FooFuncOne返回值为5
    EXPECT_CALL(mockFoo, FooFuncOne((::testing::_)))
        .WillRepeatedly((::testing::Return)(5));
}

上面mock方法主要用到了虚函数重写,但是对于非虚函数,上述写法是不能够生效的,需要有较大的改动,可以根据实际需要选择。下面贴了使用的示例:

// foo.h
class Foo {
public:
    void CallSelfMethod();
    void PublicMethod();
protected:
    void ProtectedMethod();
private:
    void PrivateMethod();
};

// 重构成模板类 foo_testable.h
template <typename T>
class FooTestable {
public:
    FooTestable(T &self);
    void CallSelfMethod();
    void PublicMethod();
protected:
    void ProtectedMethod();
private:
    void PrivateMethod();
    T &self;
};

// foo_test.cc
class MockFoo {
public:
    MOCK_METHOD(PublicMethod, void());
    MOCK_METHOD(ProtectedMethod, void());
    MOCK_METHOD(PrivateMethod, void());
};
TEST(Test_MockSelfNonVirtualMethod, SelfMethod) {
    MockFoo mockFoo;
    FooTestable<MockFoo> fooTestable(mockFoo);
    EXPECT_CALL(mockFoo, PublicMethod()).Times(1);
    EXPECT_CALL(mockFoo, ProtectedMethod()).Times(1);
    EXPECT_CALL(mockFoo, PrivateMethod()).Times(1);
    fooTestable.CallSelfMethod();
}

4. 常用断言宏

在这里插入图片描述

5. 参考资料

  1. gmock官方入门/进阶文档
    googlemock/docs/ForDummies.md
    googlemock/docs/CookBook.md
  2. 博客
    mock非虚函数
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
首先,确保你已经安装了 GTest 框架。如果没有安装,请参考 GTest 官方文档进行安装。 接下来,假设你已经有一个名为 `my_project` 的 C++ 项目,并且该项目的根目录下已经有一个名为 `src` 的文件夹用于存放源代码,一个名为 `include` 的文件夹用于存放头文件。 1. 在项目根目录下创建一个名为 `test` 的文件夹,用于存放测试代码。 2. 在 `test` 目录下创建一个名为 `CMakeLists.txt` 的文件,并添加以下内容: ``` cmake_minimum_required(VERSION 3.10) project(my_project_tests) set(CMAKE_CXX_STANDARD 11) # 添加 GTest 库 find_package(GTest REQUIRED) include_directories(${GTEST_INCLUDE_DIRS}) # 添加测试文件 add_executable(my_project_tests test/test_main.cpp # 添加其他测试文件 ) # 链接 GTest 库 target_link_libraries(my_project_tests ${GTEST_LIBRARIES} pthread) # 添加测试 add_test(NAME my_project_tests COMMAND my_project_tests) ``` 这段代码使用 CMake 构建了一个名为 `my_project_tests` 的测试项目,并链接了 GTest 库,同时添加了一个名为 `my_project_tests` 的测试。请确保将 `test/test_main.cpp` 替换为你实际的测试文件。 3. 在 `test` 目录下创建一个名为 `test_main.cpp` 的文件,并添加以下内容: ``` #include <gtest/gtest.h> int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ``` 这段代码是测试用例的入口文件,它使用 GTest 框架初始化测试环境,并运行所有测试用例。 4. 在 `test` 目录下创建一个名为 `test_sample.cpp` 的文件,并添加以下内容: ``` #include <gtest/gtest.h> TEST(SampleTest, Test1) { EXPECT_EQ(2 + 2, 4); } TEST(SampleTest, Test2) { EXPECT_TRUE(true); } ``` 这段代码是一个简单的测试用例,它包含两个测试。第一个测试用例测试 2 + 2 是否等于 4,第二个测试用例测试 true 是否为真。 5. 在项目根目录下打开终端,输入以下命令编译测试项目: ``` mkdir build cd build cmake .. make ``` 6. 运行测试用例,输入以下命令: ``` ./my_project_tests ``` 如果一切顺利,你应该能够看到测试结果输出: ``` [==========] Running 2 tests from 1 test suite. [----------] Global test environment set-up. [----------] 2 tests from SampleTest [ RUN ] SampleTest.Test1 [ OK ] SampleTest.Test1 (0 ms) [ RUN ] SampleTest.Test2 [ OK ] SampleTest.Test2 (0 ms) [----------] 2 tests from SampleTest (0 ms total) [----------] Global test environment tear-down [==========] 2 tests from 1 test suite ran. (0 ms total) [ PASSED ] 2 tests. ``` 这就是如何将 GTest 框架加入到你的 C++ 项目中进行单元测试
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值