探索 Google Test: 从基础断言到高级 Mock 技巧

Google Test 是一个广泛使用的 C++ 单元测试框架, 它提供了强大的功能, 友好的断言语法和易于集成的特性. 以下是如何使用 Google Test 测试 C++ 代码的详细介绍:

基本用法

Google Test 提供了一系列断言方法, 用于测试不同类型的值. 以下是一些常见用法:

测试整型或布尔值

#include <gtest/gtest.h>

int Add(int a, int b) { return a + b; }

int Subtract(int a, int b) { return a - b; }

TEST(BasicUsageTest, TestIntegerAndBoolean) {
  EXPECT_EQ(Add(2, 3), 5);        // 测试是否等于
  EXPECT_NE(Add(2, 2), 5);        // 测试是否不等于
  EXPECT_GT(Subtract(10, 5), 3);  // 测试是否大于
  EXPECT_LT(Subtract(5, 10), 0);  // 测试是否小于
  ASSERT_TRUE(Add(1, 1) == 2);    // 测试布尔表达式为真
  ASSERT_FALSE(Add(1, 1) == 3);   // 测试布尔表达式为假
}

比较字符串

// string compare
std::string GetGreeting() { return "Hello, World!"; }

TEST(BasicUsageTest, TestStringComparison) {
  EXPECT_STREQ(GetGreeting().c_str(), "Hello, World!");  // 测试字符串是否相等
  EXPECT_STRNE(GetGreeting().c_str(), "Hi, World!");  // 测试字符串是否不等
}

比较浮点数

比较浮点数时不能直接使用 EXPECT_EQ, 因为有如下的限制:

  1. 舍入误差: 浮点数运算可能会引入舍入误差, 例如, 1.0 / 3.0 的结果在双精度下表示为 0.3333333333333333, 但期望值可能是 0.33333333. 直接用 EXPECT_EQ 会因为微小的误差导致测试失败.
  2. 环境依赖性: 不同的编译器, 硬件架构或优化级别可能会影响浮点运算结果的精度.
  3. 浮点精度限制:浮点数有固定的有效位数, 因此无法精确表示某些小数, 例如 0.1 在二进制中是一个无限循环的值.
// float point compare
double Divide(double a, double b) { return a / b; }

TEST(BasicUsageTest, TestFloatingPointComparison) {
  EXPECT_FLOAT_EQ(Divide(1.0, 3.0), 0.33333333f);  // 测试浮点数是否相等
  EXPECT_NEAR(Divide(10.0, 3.0), 3.333, 0.001);  // 测试浮点数是否接近
}

高级用法

测试异常

测试代码是否抛出异常以及异常的内容:

#include <gtest/gtest.h>

#include <stdexcept>

void ThrowIfNegative(int value) {
  if (value < 0) {
    throw std::invalid_argument("Negative value not allowed");
  }
}

TEST(BasicUsageTest, TestExceptions) {
  // 测试是否抛出指定类型的异常
  EXPECT_THROW(ThrowIfNegative(-1), std::invalid_argument);

  // 测试是否抛出任意异常
  EXPECT_ANY_THROW(ThrowIfNegative(-2));

  // 测试是否不抛出异常
  EXPECT_NO_THROW(ThrowIfNegative(1));
}

测试退出

测试代码是否调用 exit 终止:

#include <gtest/gtest.h>

#include <cstdlib>

// 遇到0则进程退出
void TerminateIfZero(int value) {
  if (value == 0) {
    std::exit(EXIT_FAILURE);
  }
}

TEST(BasicUsageTest, TestExit) {
  // 测试是否退出并匹配退出码
  EXPECT_EXIT(TerminateIfZero(0), ::testing::ExitedWithCode(EXIT_FAILURE), "");

  // 测试正常情况是否不退出
  EXPECT_NO_THROW(TerminateIfZero(1));
}

模拟 (Mock) 代码

假设有一个纯虚接口类, 用于定义一个简单的数据库操作:

#pragma once
#include <string>

class DBInterface {
 public:
  virtual bool Connect(const std::string& url) = 0;
  virtual std::string Query(const std::string& query) = 0;

  // 必须要有一个virtual的析构函数
  virtual ~DBInterface() = default;
};

同时, 在应用中这样使用该接口:

#pragma once
#include "interface.h"

class App {
 public:
  explicit App(DBInterface* link) : db_(link) {}

  void Setup(const std::string& url) {
    if (!db_->Connect(url)) {
      return;
    }

    auto config = db_->Query("select config from config_table");
    // ... 其他处理
  }

 private:
  DBInterface* db_ = nullptr;
};

可以使用 Google Mock 模拟这个接口:

#include <gmock/gmock.h>

class MockDatabase : public Database {
public:
    MOCK_METHOD(bool, Connect, (const std::string& url), (override));
    MOCK_METHOD(std::string, Query, (const std::string& query), (override));
};

在测试的时候则可以传进去我们的Mock类实现.

TEST(MockTest, TestConnectFail) {
  MockDatabase db;

  // 设置返回值为false, 用来模拟调用失败
  EXPECT_CALL(db, Connect("test_url"))
      .Times(1)
      .WillOnce(::testing::Return(false));

  App app(&db);
  app.Setup("test_url");
}

TEST(MockTest, TestConnectSuccess) {
  MockDatabase db;

  // 设置返回值为true, 模拟调用成功
  EXPECT_CALL(db, Connect("success_url"))
      .Times(1)
      .WillOnce(::testing::Return(true));

  // APP中的逻辑, 在Connect成功之后会进行Query
  EXPECT_CALL(db, Query("select config from config_table"))
      .Times(1)
      .WillOnce(::testing::Return("mocked result"));

  App app(&db);
  app.Setup("success_url");
}

可以看到我们分别模拟了成功/失败的情况, 这种时候我们不需要真实的数据库, 这样一方面不方便另一方面也不稳定, 不能控制其返回值.

总结

Google Test 是一个功能强大且灵活的 C++ 测试框架, 适合从基础的断言测试到高级的 Mock 行为模拟. 通过合理使用 Google Test 的功能, 开发者可以轻松构建健壮的单元测试体系, 确保代码质量, 并快速定位和修复潜在问题.

源码链接

源码链接