Post

Google Test 测试夹具完全指南

Google Test 测试夹具完全指南

引言

在编写单元测试时,我们经常会遇到这样的场景:多个测试需要相同的初始化代码、相同的测试数据,或者需要访问和操作相同的资源。如果把这些代码重复写在每个测试中,不仅代码冗余,而且难以维护。

这就是 测试夹具 (Test Fixture) 发挥作用的地方。Google Test (gtest) 提供了多种类型的测试夹具,从简单的每个测试独立初始化,到测试套件级别的资源共享,再到参数化和类型化测试。本文将深入探讨 gtest 中各类测试夹具的特点、差异、使用场景,并提供完整的示例代码。

一、基础测试夹具:TEST_F

1.1 什么是基础测试夹具

TEST_F 是 gtest 中最基础、最常用的测试夹具。它的核心思想是:为每个测试创建独立的夹具对象,确保测试之间完全隔离。

1.2 工作原理

Fixture Object 1Fixture Object 2Fixture Object 3SetUp() → Test1() → TearDown()SetUp() → Test2() → TearDown()SetUp() → Test3() → TearDown()

关键特点:

  • 每个测试运行前创建新的夹具对象
  • 每个测试有独立的成员变量副本
  • SetUp() 在每个测试前执行
  • TearDown() 在每个测试后执行
  • 测试之间完全隔离,互不影响

1.3 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <gtest/gtest.h>
#include <vector>

class VectorTest : public ::testing::Test {
protected:
    void SetUp() override {
        vec_.push_back(1);
        vec_.push_back(2);
        vec_.push_back(3);
    }

    void TearDown() override {
        vec_.clear();
    }

    std::vector<int> vec_;
};

TEST_F(VectorTest, SizeIsThree) {
    EXPECT_EQ(vec_.size(), 3);
}

TEST_F(VectorTest, FirstElementIsOne) {
    EXPECT_EQ(vec_[0], 1);
    vec_.push_back(4);
    EXPECT_EQ(vec_.size(), 4);
}

TEST_F(VectorTest, StillSizeThree) {
    EXPECT_EQ(vec_.size(), 3);
}

1.4 适用场景

推荐使用:

  • 需要每个测试有干净初始状态的单元测试
  • 测试可能会修改共享状态
  • 防止测试间相互影响
  • 大多数常规单元测试

二、测试套件级共享夹具

2.1 为什么需要套件级共享

某些资源的创建成本非常高,比如数据库连接、大型文件加载、网络服务初始化。如果每个测试都创建一次,测试运行时间会显著增加。

2.2 工作原理

Shared ResourceSetUpTestSuite() 只执行一次TearDownTestSuite() 只执行一次Test 1Test 2Test 3访问访问访问

关键特点:

  • 静态成员变量在所有测试间共享
  • SetUpTestSuite() 只在第一个测试前执行一次
  • TearDownTestSuite() 只在最后一个测试后执行一次

2.3 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <gtest/gtest.h>

class DatabaseTest : public ::testing::Test {
protected:
    static void SetUpTestSuite() {
        db_connection_ = ConnectToDatabase();
    }

    static void TearDownTestSuite() {
        CloseDatabaseConnection(db_connection_);
    }

    void SetUp() override {
        BeginTransaction(db_connection_);
    }

    void TearDown() override {
        RollbackTransaction(db_connection_);
    }

    static DBHandle db_connection_;
};

DBHandle DatabaseTest::db_connection_ = nullptr;

TEST_F(DatabaseTest, QueryUser) {
    auto result = ExecuteQuery(db_connection_, "SELECT * FROM users");
    EXPECT_TRUE(result.IsValid());
}

三、全局环境夹具

3.1 全局级别的设置

当需要在 整个测试程序 级别进行初始化和清理时,可以使用 ::testing::Environment

3.2 工作原理

Environment::SetUp()Test 1Test 2Environment::TearDown()Test Suite 1Test Suite 2

3.3 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <gtest/gtest.h>

class GlobalEnvironment : public ::testing::Environment {
public:
    void SetUp() override {
        InitializeLoggingSystem();
        InitializeConfiguration();
    }

    void TearDown() override {
        ShutdownLoggingSystem();
    }
};

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    ::testing::AddGlobalTestEnvironment(new GlobalEnvironment);
    return RUN_ALL_TESTS();
}

四、值参数化测试夹具

4.1 参数化测试的价值

当你需要用 多组输入数据 验证 相同的测试逻辑 时,参数化测试是最佳选择。

4.2 工作原理

Test LogicParameter GeneratorIsPrime() Test235711GetParam()

4.3 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <gtest/gtest.h>

class PrimeTest : public ::testing::TestWithParam<int> {
protected:
    bool IsPrime(int n) {
        if (n <= 1) return false;
        if (n == 2) return true;
        if (n % 2 == 0) return false;
        for (int i = 3; i * i <= n; i += 2) {
            if (n % i == 0) return false;
        }
        return true;
    }
};

TEST_P(PrimeTest, ReturnsTrueForPrimes) {
    int n = GetParam();
    EXPECT_TRUE(IsPrime(n));
}

INSTANTIATE_TEST_SUITE_P(
    PrimeNumbers,
    PrimeTest,
    ::testing::Values(2, 3, 5, 7, 11, 13, 17)
);

五、类型化测试夹具

5.1 为什么需要类型化测试

当你需要验证 多种类型 是否满足 相同的接口契约 时,类型化测试非常有用。

5.2 工作原理

Test TemplateType ListPush TestPop TestSize Teststd::vector<int>std::list<int>std::deque<int>instantiateinstantiateinstantiateinstantiateinstantiateinstantiateinstantiateinstantiateinstantiate

5.3 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <gtest/gtest.h>
#include <vector>
#include <list>
#include <deque>

template <typename T>
class ContainerTest : public ::testing::Test {
protected:
    T container_;
};

using ContainerTypes = ::testing::Types<
    std::vector<int>,
    std::list<int>,
    std::deque<int>
>;

TYPED_TEST_SUITE(ContainerTest, ContainerTypes);

TYPED_TEST(ContainerTest, IsEmptyInitially) {
    EXPECT_TRUE(this->container_.empty());
}

TYPED_TEST(ContainerTest, SizeIncreasesAfterPush) {
    this->container_.push_back(42);
    EXPECT_EQ(this->container_.size(), 1);
}

六、类型参数化测试夹具

6.1 与类型化测试的区别

类型化测试 (TYPED_TEST):

  • 定义测试时就指定类型列表
  • 适合在同一编译单元内完成所有测试

类型参数化测试 (TYPED_TEST_P):

  • 先定义测试逻辑模板
  • 后实例化类型列表
  • 支持在不同编译单元多次实例化
  • 适合创建可重用的测试库

6.2 工作原理

Test LibraryProject A TypesProject B TypesTest Pattern ATest Pattern BTest Pattern CMyClass1MyClass2YourClass1YourClass2instantiateinstantiate

6.3 使用示例

首先创建头文件定义测试模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// container_test.h
#pragma once
#include <gtest/gtest.h>

template <typename T>
class ContainerTest : public ::testing::Test {
protected:
    T container_;
};

TYPED_TEST_SUITE_P(ContainerTest);

TYPED_TEST_P(ContainerTest, IsEmptyInitially) {
    EXPECT_TRUE(this->container_.empty());
}

TYPED_TEST_P(ContainerTest, SizeIncreasesAfterPush) {
    this->container_.push_back(42);
    EXPECT_EQ(this->container_.size(), 1);
}

REGISTER_TYPED_TEST_SUITE_P(ContainerTest,
    IsEmptyInitially,
    SizeIncreasesAfterPush
);

然后在不同编译单元中实例化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// project_a_test.cpp
#include "container_test.h"
#include <vector>
#include <list>

using ProjectATypes = ::testing::Types<
    std::vector<int>,
    std::list<int>
>;

INSTANTIATE_TYPED_TEST_SUITE_P(
    ProjectA,
    ContainerTest,
    ProjectATypes
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// project_b_test.cpp
#include "container_test.h"
#include <deque>
#include <queue>

using ProjectBTypes = ::testing::Types<
    std::deque<int>,
    std::queue<int>
>;

INSTANTIATE_TYPED_TEST_SUITE_P(
    ProjectB,
    ContainerTest,
    ProjectBTypes
);

七、各类夹具对比与选择指南

7.1 特性对比表

夹具类型 生命周期 隔离性 创建成本 主要用途
基础夹具 每个测试 ⭐⭐⭐⭐⭐ 完全隔离 通用单元测试 TEST_F
套件级共享 整个测试套件 ⭐⭐ 共享静态状态 昂贵资源复用 SetUpTestSuite
全局环境 整个测试程序 ⭐ 全局共享 程序级初始化 Environment
值参数化 每个参数实例 ⭐⭐⭐⭐ 参数隔离 多输入测试 TEST_P
类型化测试 每个类型实例 ⭐⭐⭐⭐ 类型隔离 多类型接口测试 TYPED_TEST
类型参数化 实例化时确定 ⭐⭐⭐⭐ 可扩展 抽象测试库 TYPED_TEST_P

7.2 决策流程图

需要测试多种类型?需要创建可重用测试库?类型参数化测试(TYPED_TEST_P)类型化测试(TYPED_TEST)需要多组输入数据?值参数化测试(TEST_P)有昂贵共享资源?跨多个测试套件?全局环境(Environment)套件级共享(SetUpTestSuite)基础测试夹具(TEST_F)

八、最佳实践与注意事项

8.1 构造函数 vs SetUp()

问:为什么要用 SetUp() 而不是构造函数?

答: 构造函数中不能使用 ASSERT_* 宏,而且如果构造函数抛出异常,测试框架无法正确处理。

8.2 测试命名规范

1
2
3
4
5
// ✅ 好的命名
TEST_F(VectorTest, PushBackIncreasesSize) { ... }

// ❌ 不好的命名
TEST_F(VectorTest, Test1) { ... }

8.3 避免测试间依赖

⚠️ 重要: 测试应该是独立的,不应该依赖执行顺序。

总结

Google Test 提供了丰富的测试夹具机制,覆盖了从简单到复杂的各种测试场景:

  1. TEST_F - 最常用,每个测试独立状态
  2. SetUpTestSuite - 测试套件级资源共享
  3. Environment - 全局级别的初始化
  4. TEST_P - 值参数化数据驱动测试
  5. TYPED_TEST - 类型化多接口测试
  6. TYPED_TEST_P - 可重用的抽象测试库

核心原则:

  • 优先选择隔离性高的夹具(TEST_F)
  • 只在必要时共享资源
  • 保持测试独立,不依赖执行顺序
  • 命名清晰,测试名称描述行为

选择合适的测试夹具,可以让你的测试代码更清晰、更易维护、执行效率更高。

This post is licensed under CC BY 4.0 by the author.

© . Some rights reserved.

Using the Chirpy theme for Jekyll.