当前位置: 首页 > 工具软件 > Gmock > 使用案例 >

GTEST/GMOCK介绍与实战

韩智敏
2023-12-01

一、简介

gtest全称是google test

  • 是一个C++测试框架
  • gmock是和gtest一起用的mock框架
  • gtest使用教程:gtest工程代码下的googletest/docs/index.md
  • gtest进阶教程:googletest/docs/advanced.md
  • 所有例子说明:googletest/docs/samples.md

二、GoogleTest安装

下载链接

ubuntu20.04见:

sudo apt-get install libgtest-dev  libgmock-dev

三、GoogleTest 常见概念

1.断言

GoogleTest 中,是通过断言(Assertion)来判断代码实现的功能是否符合预期。断言的结果分为成功(success)、非致命错误(non-fatal failture)和致命错误(fatal failture)。

成功:断言成功,程序的行为符合预期,程序允许继续向下运行。可以在代码中调用 SUCCEED() 来表示这个结果。
非致命错误:断言失败,但是程序没有直接中止,而是继续向下运行。可以在代码中调用 FAIL() 来表示这个结果。
致命错误:中止当前函数或者程序崩溃。可以在代码中调用 ADD_FAILURE() 来表示这个结果。

GoogleTest 的断言是类似于函数调用的宏。通过对其行为进行断言来测试一个类或函数。当断言失败时,GoogleTest 会打印断言的源文件和行号位置以及失败消息。还可以提供自定义失败消息,该消息将附加到 GoogleTest 的消息中。

2.EXPECT 与 ASSERT

宏的格式有两种:

EXPECT_*:在失败时会产生非致命故障,不会中止当前功能。
ASSERT_*:在失败时会产生致命错误,并中止当前函数,同一用例后面的语句将不再执行。

通常 EXPECT_* 是首选,因为它们允许在测试中报告多个。但是如果在某条件不成立,程序就无法运行时,就应该使用 ASSERT_*。

另一方面,因为 ASSERT_* 是直接从当前函数返回的,可能会导致一些内存、文件资源没有释放,因此存在内存泄漏的问题。

GoogleTest 提供了一组断言,用于以各种方式验证代码的行为。包括检查布尔条件、基于关系运算符比较值、验证字符串值、浮点值等等。甚至还有一些断言可以通过提供自定义谓词来验证更复杂的状态。有关 GoogleTest 提供的断言的完整列表,可以参考官方文档:Assertions。

3.自定义失败信息

如果想要提供自定义的失败信息,只需要使用流操作符 << 将这些信息流到断言宏中,例如:

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

任何可以流向 std::ostream 的东西都可以流向断言宏,特别是 C 语言的字符串(char*)和 std::string 对象。如果一个宽字符串(wchar_t*,Windows 上 UNICODE 模式下的 TCHAR*,或 std::wstring)被流向一个断言,它将在打印时被转换成 UTF-8。

4.功能测试

TEST
如果想要创建测试,可以使用宏函数 TEST,它具有以下特点:

TEST 是一个没有返回值的宏函数。
我们可以在该函数中使用断言来检测代码是否有效,测试的结果由断言决定。如果测试中的任何断言失败(致命或非致命),或者如果测试崩溃,则整个测试失败。否则,它会成功。

函数定义如下:

TEST(TestSuiteName, TestName) {
  ... test body ...
}

TestSuiteName 对应测试用例集名称,TestName 是归属的测试用例名称。这两个名称都必须是有效的 C++ 标识符,并且它们不应包含任何下划线。测试的全名由其包含的测试用例集及其测试名称组成。来自不同测试用例集的测试可以具有相同的名称。

TEST_F
如果发现自己编写了两个或多个对相似数据进行操作的测试,可以使用 test fixture 来为多个测试重用这些相同的配置。

fixture,其语义是固定的设施,而 test fixture 在 GoogleTest 中的作用就是为每个 TEST 都执行一些同样的操作。

其对应的宏函数是TEST_F,函数定义如下:

TEST_F(TestFixtureName, TestName) {
  ... test body ...
}

TestFixtureName 对应一个 test fixture 类的名称。因此我们需要自己去定义一个这样的类,并让它继承testing::Test类,然后根据我们的需要实现下面这两个虚函数:

virtual void SetUp():类似于构造函数,在 TEST_F 之前运行;
virtual void TearDown():类似于析构函数,在 TEST_F 之后运行。

此外testing::Test还提供了两个 static 函数:

static void SetUpTestSuite():在第一个 TEST 之前运行
static void TearDownTestSuite():在最后一个 TEST 之后运行

除了这两种,还有一种全局事件,即继承testing::Environment,并实现下面两个虚函数:

virtual void SetUp():在所有用例之前运行;
virtual void TearDown():在所有用例之后运行。

运行测试
与其他 C++ 框架不同,TEST 和 TEST_F 会隐式向 GoogleTest 注册这些测试函数,因此我们不需要为了运行这些它们而进行枚举。

定义完测试后,你可以用 RUN_ALL_TESTS 来运行它们,此时会运行所有的测试,如果全部成功则返回 0,反之则返回 1。其执行流程如下:

1,保存所有 GoogleTest 标志的状态。
2,为第一个测试创建一个 test fixture 对象。
3,通过 SetUp 初始化 test fixture 对象。
4,在 test fixture 对象上进行测试。
5,通过 TearDown 清理 test fixture。
6,删除 test fixture。
7,恢复所有 GoogleTest 标志的状态。
8,对下一个测试重复上述步骤,直到所有测试都运行完毕。

如果发生致命故障,则将跳过后续步骤。

编写 main 函数

  • 用户不需要编写自己的 main 函数,编译器会自动将它链接到 gtest_main。如果用户有特殊需求,需要编写一个 main 函数,则需要在返回时调用 RUN_ALL_TESTS() 来运行所有测试,例如:
int main(int argc, char **argv) {
  printf("Running main() from %s\n", __FILE__);
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS(); 
}

testing::InitGoogleTest函数会解析 GoogleTest 标志的命令行,并删除所有识别的标志。这允许用户通过各种标志控制测试程序的行为。您必须在调用RUN_ALL_TESTS之前调用此函数 ,否则标志将无法正确初始化。

四、使用示例

1.Writing the main() Function

  • ref:googletest/docs/primer.md

main.cpp

#include <gtest/gtest.h>

int main(int argc,  char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

test.cpp

#include <gtest/gtest.h> 
int Foo(int a, int b)
{
    return a + b;
}

TEST(FooTest, FooAddTest)
{
    EXPECT_EQ(2, Foo(1, 1));
    EXPECT_EQ(6, Foo(2, 4));
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.0.0)
project(hello_gtest VERSION 0.1.0)


add_executable(${PROJECT_NAME} main.cpp test.cpp)
target_link_libraries(${PROJECT_NAME} gtest pthread)

#等价于
add_executable(${PROJECT_NAME}  test.cpp)
target_link_libraries(${PROJECT_NAME} gtest_main gtest pthread)

2.Using Mocks in Tests

  • ref:googletest/docs/gmock_for_dummies.md

CMakeLists.txt

cmake_minimum_required(VERSION 3.0.0)
project(hello_gtest VERSION 0.1.0)

add_executable(${PROJECT_NAME} FooMain.cc man.cc)
target_link_libraries(${PROJECT_NAME} gtest gmock pthread)

FooInterface.h

#ifndef FOOINTERFACE_H_
#define FOOINTERFACE_H_

#include <string>


class FooInterface {
public:
        virtual ~FooInterface() {}

public:
        virtual std::string getArbitraryString() = 0;
};


#endif // FOOINTERFACE_H_

FooMain.cc

#include <cstdlib>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <iostream>
#include <string>
#include "FooInterface.h"
using namespace std;

using ::testing::Return;

class MockFoo : public FooInterface
{
public:
    MOCK_METHOD(std::string, getArbitraryString, ());
};

TEST(CASETEST, Case)
{

    string value = "Hello World!";

    // 第18行,声明一个MockFoo的对象:mockFoo
    MockFoo mockFoo;
    EXPECT_CALL(mockFoo, getArbitraryString()).Times(1).WillOnce(Return(value));

    // 是为MockFoo的getArbitraryString()方法定义一个期望行为,其中Times(1)的意思是运行一次,WillOnce(Return(value))的意思是第一次运行时把value作为getArbitraryString()方法的返回值
    string returnValue = mockFoo.getArbitraryString();
    cout << "Returned Value: " << returnValue << endl;
}

man.cc

#include <gtest/gtest.h>
#include <gmock/gmock.h>

int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

3.跳过某个case

在对应case前面,其名称中添加DISABLED_前缀

// Tests that Foo does Abc.
TEST(FooTest, DISABLED_DoesAbc) { ... }

4.gtest-coverage

测试代码参考

 类似资料: