引言:为什么没有模拟和夹具的测试就像抽奖
\n\n想象一下,你在为宇宙飞船编写代码。你的函数通过调用引擎控制模块来计算轨迹。如何在不启动真实引擎的情况下验证轨迹是否正确?在现实世界中,这将是灾难。在C++的世界里,这是模拟对象和测试夹具的任务。
\n\n如果你曾经用C++编写过单元测试,你肯定遇到过这些问题:测试因网络、数据库或文件系统而失败。或者你为了每次检查而花费10行代码来准备数据。熟悉吗?那么这篇文章就是为你准备的。
\n\n我们将探讨C++中的模拟对象如何帮助将代码与外部世界隔离,而测试夹具则帮助重用测试设置。没有废话,只有代码和实践。开始吧!
\n\n什么是模拟对象以及为什么在C++中需要它们
\n\n模拟对象(或简称模拟)是真实对象的替代品,它模仿真实对象的行为。你告诉模拟:“当调用getData()方法时,返回42”。然后它乖乖地返回42,而不去访问数据库、API或文件。
\n\n在C++中,模拟尤其重要,因为这种语言不容忍错误。如果你的类依赖于网络,而网络宕机了——测试就会失败,尽管你的代码是完美的。模拟解决了这个问题:它们隔离了被测试的代码。
\n\nC++中模拟对象的主要使用场景:
\n- \n
- 外部服务(数据库、REST API、文件)。 \n
- 复杂计算,会拖慢测试速度(加密、渲染)。 \n
- 非确定性函数(rand()、time())。 \n
- 交互验证:方法是否被调用、调用次数、以及调用参数。 \n
在C++中创建模拟的事实标准是Google Mock库(也称为gmock),它与Google Test捆绑在一起。但对于小型项目,手动编写模拟甚至更简单。
\n\n测试夹具:为测试搭建舞台
\n\n测试夹具是一组执行测试所需的对象和数据。与其每次都写:
\n\nMyClass obj;\nobj.init("data");\nobj.connect();\n\n……不如将其提取到夹具中。Google Test通过继承::testing::Test来支持夹具。这不仅方便——更是C++测试的最佳实践。
测试夹具解决三个问题:
\n- \n
- 代码重用——一次设置,所有测试使用。 \n
- 测试清洁性——每个测试从相同的状态开始。 \n
- 自动清理——如果需要释放资源,可以在夹具的析构函数中完成。 \n
现在,让我们在一个例子中将模拟和夹具结合起来。
\n\n编写包含模拟对象和测试夹具的测试:真实示例
\n\n假设我们有一个WeatherReporter类,它通过API获取天气数据。我们想测试getTemperature()方法,而不访问真实服务器。
步骤1. 定义接口
\n\n为了插入模拟,我们需要一个抽象类。这是关键点:模拟通过多态性工作。
\n\n// weather_api.h\nclass WeatherAPI {\npublic:\n virtual ~WeatherAPI() = default;\n virtual double getTemperature(const std::string& city) = 0;\n virtual bool isAvailable() = 0;\n};\n\n步骤2. 使用Google Mock实现模拟
\n\n// mock_weather_api.h\n#include <gmock/gmock.h>\n#include "weather_api.h"\n\nclass MockWeatherAPI : public WeatherAPI {\npublic:\n MOCK_METHOD(double, getTemperature, (const std::string& city), (override));\n MOCK_METHOD(bool, isAvailable, (), (override