当前位置: 首页 > article >正文

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

文章目录

    • 基本用法
      • 测试整型或布尔值
      • 比较字符串
      • 比较浮点数
    • 高级用法
      • 测试异常
      • 测试退出
      • 模拟 (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 的功能, 开发者可以轻松构建健壮的单元测试体系, 确保代码质量, 并快速定位和修复潜在问题.

源码链接

源码链接


http://www.kler.cn/a/467847.html

相关文章:

  • 产品经理-竞品分析
  • 202-01-06 Unity 使用 Tip1 —— UnityHub 模块卸载重装
  • JavaScript语言的编程范式
  • 练习(继承)
  • 【C++项目实战】类和对象入门实践:日期类实现万字详解
  • SAP 01-初识AMDP(ABAP-Managed Database Procedure)
  • js canvas绘制五星红旗
  • Outlook2024版如何回到经典Outlook
  • Windows 11 上通过 WSL (Windows Subsystem for Linux) 安装 MySQL 8
  • html+css+js网页设计 美食 美食天下2个页面(里面包含php和mysql)
  • Launcher3主页面加载显示流程分析
  • ROS节点架构设计:提高模块化与可扩展性
  • 算法解析-经典150(区间、栈)
  • 【通识安全】应急救护常识23则
  • C++软件设计模式之访问者模式
  • Linux 异步 I/O 框架 io_uring:基本原理、程序示例与性能压测
  • Android SPRD 工模测试修改
  • boot-126网易邮件发送
  • CSS系列(47)-- Animation Timeline详解
  • 车载软件架构 ---互联网人才怎么转变成汽车软件专家?
  • 【网络协议】开放式最短路径优先协议OSPF详解(三)
  • OSError: [WinError 126] 找不到指定的模块 backend_with_compiler.dll
  • 文件I/O - 文件读写操作
  • 计算机网络 —— 网络编程实操(1)(UDP)
  • C#利用Attribute实现面向切面编程(AOP)
  • LangChain4j 框架探索