TDD测试驱动学习

article/2025/10/11 19:54:02

gtest 和 gmock 安装

在这里插入图片描述

在这里插入图片描述


//如果不知道对应库名字可以执行这个命令查找对应库,如果没找到要去跟新对应的源sudo apt update
sudo apt-cache search gtest 
sudo apt-cache search gmock 

测试例子


#include <string>
using std::string;// 定义 Soundex 类
class Soundex {public:// 对给定字符串进行编码string encode(const string& str) const { return zeroPad(str); }private:// 在字符串末尾填充零,使其达到一定长度string zeroPad(const string& word) const { return word + "000"; }
};#include "gmock/gmock.h"
#include "gtest/gtest.h"// 定义 SoundexEncoding 测试套件,用于测试 Soundex 类的编码功能
TEST(SoundexEncoding, RetainSoleLetterOfOneLetterWord) {// 创建一个 Soundex 对象Soundex soundex;// 对单个字母进行编码auto encoded = soundex.encode("A");// 断言编码后的结果与期望值相等ASSERT_THAT(encoded, testing::Eq("A"));
}// 主函数
int main() {// 初始化 Google Test 框架testing::InitGoogleTest();// 运行所有测试return RUN_ALL_TESTS();
}

TDD 的三条规则是:

  1. 在编写新功能之前,先编写测试。
  2. 仅编写足以使测试 Pass 的代码。当测试已经通过时,不要写更多的代码。
  3. 重构现有的代码以消除重复和提高可读性

MATCHER_P 宏定义自定义匹配器


// 定义一个名为 IsEven 的自定义匹配器
MATCHER_P(IsEven, "", "") { // 匹配器名称为 "IsEven",没有自定义描述和失败消息return (arg % 2) == 0; // 如果 arg 是偶数,则匹配成功
}// 创建测试用例,验证偶数是否匹配自定义匹配器
TEST(MyTest, TestEvenNumbers) {int num = 4;EXPECT_THAT(num, IsEven()); // 使用 EXPECT_THAT 宏验证 num 是否匹配 IsEven 匹配器
}// 创建测试用例,验证奇数是否不匹配自定义匹配器
TEST(MyTest, TestOddNumbers) {int num = 7;EXPECT_THAT(num, Not(IsEven())); // 使用 EXPECT_THAT 和 Not 宏,验证 num 是否与 IsEven 匹配器的结果相反
}

–gtest_filter

是该 --gtest_filter 是该工具中的一个参数选项,用于过滤 Google Mock 测试用例。工具中的一个参数选项,用于过滤 Google Mock 测试用例。
假设我们有一个测试用例集 TestSuite,其中包含以下测试用例:


TEST(TestSuite, Test1) {}
TEST(TestSuite, Test2) {}
TEST(TestSuite, AnotherTest) {}

如果我们只想运行 Test1 和 AnotherTest 这两个测试用例,可以使用以下命令:


./mytest --gtest_filter="TestSuite.Test1|TestSuite.AnotherTest"

这个命令会运行 Test1 和 AnotherTest,而忽略 Test2。

ASSERT_THAT


string actual = string("al") + "pha";
//判断实际值actual是否与期望值“alpha”相等
//如果实际值等于期望值,则通过测试 
ASSERT_THAT(actual, Eq("alpha"));

EXPECT_CALL

用法:

  1. Match 期望的参数值 可以使用MATCHER宏定义一个匹配器,用于期望特定的参数值。例如

MATCHER_P(IsEqualToString, expected, "") { return arg == expected; }
EXPECT_CALL(mockObject, someMethod(IsEqualToString("expectedValue")));

在这个例子中,我们定义了一个IsEqualToString匹配器,用于期望someMethod方法的参数等于"expectedValue"。然后,我们将这个期望传递给EXPECT_CALL宏。

  1. Times 期望方法调用的次数 可以使用Times宏指定期望方法被调用的精确次数,例如:

EXPECT_CALL(mockObject, someMethod()).Times(3);

在这个例子中,我们期望someMethod方法被调用3次

  1. WillOnce / WillRepeatedly 模拟返回值可以使用WillOnce宏指定一次期望调用的返回值,例如:

EXPECT_CALL(mockObject, someMethod()).WillOnce(Return(1));

在这个例子中,我们期望someMethod方法被调用一次,并返回1。可以使用WillRepeatedly宏指定多次期望调用的返回值,例如:


EXPECT_CALL(mockObject, someMethod()).WillRepeatedly(Return(1));

在这个例子中,我们期望someMethod方法被多次调用,并返回1

  1. InSequence 指定方法调用顺序 可以使用InSequence宏指定期望方法调用的顺序,例如:

testing::InSequence sequence;
EXPECT_CALL(mockObject, someMethod1());
EXPECT_CALL(mockObject, someMethod2());

在这个例子中,我们期望someMethod1方法在someMethod2方法之前被调用。
这些是其中的一些用法,EXPECT_CALL还有其他用法,可以根据需要选择适合的方式。

是Google Mock测试框架提供的一个宏,指定这些函数在被调用时的动作。


#include <gmock/gmock.h>
#include <gtest/gtest.h>class HttpClient {public:// 基类HttpClient定义了虚析构函数,以使子类在释放内存时能够正确析构virtual ~HttpClient() {}// 纯虚函数sendRequest,用于在子类中实现发送HTTP请求的具体逻辑virtual void sendRequest(const std::string& url) = 0;
};
// 模拟HttpClient,实现了sendRequest方法,用于测试MyHttpClient
class MockHttpClient : public HttpClient {public:// 使用MOCK_METHOD宏定义sendRequest方法MOCK_METHOD(void, sendRequest, (const std::string& url), (override));
};class MyHttpClient {public:// 构造函数,将HttpClient对象作为依赖项传递给MyHttpClient,存储在httpClient_成员变量中MyHttpClient(HttpClient* httpClient): httpClient_(httpClient) {}// 发送请求到期望的URL的方法void sendHttpRequestToExpectedURL(const std::string& url) {// 通过httpClient_成员变量调用HttpClient对象的sendRequest方法,将url作为参数,发送HTTP请求httpClient_->sendRequest(url);}private:// HttpClient对象指针HttpClient* httpClient_;
};// 测试用例开始
TEST(MyHttpClientTest, SendsHttpRequestToCorrectUrl) {// 定义期望的URLstd::string expectedURL = "https://example.com/test";// 创建MockHttpClient实例,用于模拟HttpClient对象MockHttpClient mockHttpClient;// 创建使用HttpClient对象的实例,将其传递给MyHttpClient的构造函数MyHttpClient httpClient(&mockHttpClient);// 设置期望:MockHttpClient的sendRequest方法将以expectedURL作为参数被调用EXPECT_CALL(mockHttpClient, sendRequest(expectedURL));// 调用应该发送请求到expectedURL的方法// 调用httpClient对象的sendHttpRequestToExpectedURL方法,将expectedURL作为参数httpClient.sendHttpRequestToExpectedURL(expectedURL);
}// 定义 main 函数
int main(int argc, char** argv) {testing::InitGoogleMock(&argc, argv);  // 初始化 Google Mock 并运行所有测试return RUN_ALL_TESTS();
}

浮点数的比较

ULP 指Unit in the Last Place, 即表示连续浮点数之间的最小差异量,如果两个浮点数a和b,如果他们的差小于ULP
|a-b| < ULP
我们认为他们相等


double expected = 0.1;
double actual = 0.2 * 0.5;
// 使用ULP差异比较浮点数
EXPECT_FLOAT_EQ(expected, actual);          // 这里会失败,因为ULP差异大于0// 使用相对/绝对误差范围比较浮点数
EXPECT_NEAR(expected, actual, 0.0001);      // 这里会通过,因为绝对误差和相对误差均在0.0001之内//使用ASSERT_THAT()和DoubleEq()宏对变量x和y的和进行浮点数比较
//如果x+y与期望值4.56之间的ULP差异小于某个值,则测试通过,否则测试失败
ASSERT_THAT(x + y, DoubleEq(4.56));

基于异常测试


#include <gmock/gmock.h>
#include <gtest/gtest.h>using namespace testing;class Calculator {public:virtual int Divide(int dividend, int divisor) = 0;
};// 定义一个 MockCalculator 类,继承自 Calculator 类,
// 并使用 MOCK_METHOD 宏定义了一个名为 Divide 的虚方法
class MockCalculator : public Calculator {public:MOCK_METHOD(int, Divide, (int, int), (override));
};// 定义测试用例,名称为 CalculatorTest,测试除数为零的情况
TEST(CalculatorTest, DivideByZero) {// 创建一个 MockCalculator 实例MockCalculator mockCalculator;// 定义一个期望,表示调用 Divide 方法并传入除数为0的参数时,// 应该抛出 std::logic_error 异常,并将异常信息设置为 "Division by zero"EXPECT_CALL(mockCalculator, Divide(_, 0)).WillOnce(Throw(std::logic_error("Division by zero")));// 使用 ASSERT_THROW 宏检查调用 Divide 方法并传入参数 10 和 0 时// 是否抛出 std::logic_error 异常,并将异常信息与 "Division by zero" 进行比较ASSERT_THROW(mockCalculator.Divide(10, 0), std::logic_error);
}// 主函数
int main() {// 初始化 Google Test 框架InitGoogleTest();// 运行所有测试return RUN_ALL_TESTS();
}

ASSERT_ANY_THROW 这个会检查是不是剖出异常如果抛出,测试通过


#include <gmock/gmock.h>
#include <gtest/gtest.h>// 定义一个函数 Divide,接受两个整数参数 x 和 y,将它们相除的结果保存到 result 中
void Divide(int x, int y) {// 如果除数 y 等于 0,则抛出 std::logic_error 异常if (y == 0) {throw std::logic_error("Division by zero");}int result = x / y;// ...
}// 定义一个测试用例 ExceptionTest,测试 Divide 函数是否能够正确地抛出异常
TEST(ExceptionTest, AnyException) {// 使用 ASSERT_ANY_THROW 宏检查 Divide(10, 0) 是否抛出任何类型的异常ASSERT_ANY_THROW(Divide(10, 0));try {// 如果 Divide 函数抛出异常,会被 catch 块捕获Divide(10, 0);FAIL() << "Expected std::logic_error";} catch (const std::logic_error& e) {EXPECT_STREQ("Division by zero", e.what());} catch (...) {FAIL() << "Expected std::logic_error";}
}// 主函数
int main() {// 初始化 Google Test 框架testing::InitGoogleTest();// 运行所有测试return RUN_ALL_TESTS();
}

参数化测试

在软件开发过程中,测试(testing)和测试驱动开发(test-driving)都可以使用参数化测试(parameterized tests)等工具来增强测试的效果。测试(testing)是在开发完成后进行的测试,而测试驱动开发(test-driving)则是在开发过程中通过测试来驱动代码的开发。


#include <gtest/gtest.h>
#include <vector>
#include <tuple>
#include "calculator.hpp"using namespace std;// 定义一个测试用例,测试 Add() 方法是否能正确计算加法
class AddTest : public testing::TestWithParam<tuple<int, int, int>> {
};// 使用 TEST_P 宏定义一个参数化测试,测试 Add() 方法是否能正确计算加法
TEST_P(AddTest, TestAdd) {// 获取传入的参数int x = get<0>(GetParam());int y = get<1>(GetParam());int expected_result = get<2>(GetParam());// 创建一个 Calculator 类对象,并调用其 Add() 方法计算加法Calculator calc;int actual_result = calc.Add(x, y);// 断言计算结果是否与预期结果一致ASSERT_EQ(expected_result, actual_result);
}// 在 INSTANTIATE_TEST_CASE_P 宏中传递参数,为参数化测试提供测试数据
INSTANTIATE_TEST_CASE_P(TestAddInstantiation, AddTest, testing::Values(// 测试用例1:两个正整数相加make_tuple(2, 3, 5),// 测试用例2:两个负整数相加make_tuple(-2, -3, -5),// 测试用例3:一个正整数和一个负整数相加make_tuple(2, -3, -1),// 测试用例4:一个正整数和零相加make_tuple(2, 0, 2),// 测试用例5:两个零相加make_tuple(0, 0, 0)
));int main(int argc, char **argv) {testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

Test Doubles

是一个测试技术、概念,用于在单元测试环境中替代软件系统的依赖组件,以便更容易地测试软件系统。Test Doubles 旨在消除测试过程中的不确定性,使测试的结果更可靠。
Test Doubles 有多种类型,包括:
Dummy Object:没有实际实现的占位符对象,仅用于填充方法和参数。
Test Stubs: 测试桩它用于在测试期间提供预定义的数据或行为,以便测试系统的某些行为。


// 假设我们有一个 User 类,依赖于一个 Database 对象
class Database {
public:bool save(int id, std::string name) {std::cout << "Save data to the database" << std::endl;return true;}
};class User {
private:int id;std::string name;Database* db;public:User(int id, std::string name, Database* db) {this->id = id;this->name = name;this->db = db;}bool save() {std::cout << "Save User data" << std::endl;// 使用依赖的 Database 对象完成持久化操作bool result = db->save(id, name);return result;}
};// Test Stub
class StubDatabase : public Database {
public:// 重写 save 函数以提供预定义行为bool save(int id, std::string name) override {std::cout << "Save data to the test database" << std::endl;return true;}
};// 测试 User 的 save 函数
int main() {StubDatabase stubDb;User user(1, "testUser", &stubDb);bool result = user.save();std::cout << "Save operation result: " << result << std::endl;return 0;
}

Test Spy 可以用于在测试环境中记录被调用的函数或方法的信息,以便在之后验证测试对象的行为。


#include <iostream>
#include <string>
#include <vector>class Logger {public:virtual void write_message(const std::string& message) {// 记录系统事件的写入操作std::cout << "[Logger] " << message << std::endl;}void log_event(const std::string& event) {std::string message = "Event: " + event;write_message(message);}
};// Test Spy
class LoggerSpy : public Logger {public:std::vector<std::string> messages;void write_message(const std::string& message) override {messages.push_back(message);}
};// 测试 log_event 函数是否能够在 write_message 被调用时记录正确的消息
int main() {LoggerSpy spy;// 调用 Logger 类的方法spy.log_event("test event");// 验证消息是否被正确记录if (spy.messages.size() != 1 || spy.messages[0] != "Event: test event") {std::cout << "Test failed: incorrect message recorded" << std::endl;return 1;}std::cout << "Test passed" << std::endl;return 0;
}

Mock Objects 是模拟对象,用于在测试环境中代替依赖对象。它们是具有预定义行为的测试 double 类型,用于在测试中代替或模拟依赖项以便测试对象。Mock Objects 通常用于测试高度耦合的对象,它们提供了强大的控制和验证测试对象的方式。


// PaymentGateway 类定义
class PaymentGateway {
public:virtual bool process_payment(int amount) = 0;
};// ShoppingCart 类定义
class ShoppingCart {
public:ShoppingCart(PaymentGateway& gateway) : gateway(gateway) {}bool checkout(int amount) {// 调用外部依赖 PaymentGatewayreturn gateway.process_payment(amount);}private:PaymentGateway& gateway;
};// PaymentGatewayMock 类定义
class PaymentGatewayMock : public PaymentGateway {
public:bool process_payment(int amount) override {// 返回失败状态return false;}
};// 测试购物车在付款失败的情况下的行为
int main() {PaymentGatewayMock mock;ShoppingCart cart(mock);bool checkout_result = cart.checkout(100);// 验证购物车应该返回 falseif (checkout_result != false) {std::cout << "Test failed: incorrect checkout result" << std::endl;return 1;}std::cout << "Test passed" << std::endl;return 0;
}

下面创建一个SoundexEncoding fixture 类


#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"using std::string;// 定义 Soundex 类
class Soundex {public:// 对给定字符串进行编码string encode(const string& str) const { return zeroPad(str); }private:// 在字符串末尾填充零,使其达到一定长度string zeroPad(const string& word) const { return word + "000"; }
};// 定义一个名为SoundexEncoding的测试夹具,派生自::testing::Test类
class SoundexEncoding : public testing::Test {public:Soundex soundex;  // 声明一个公共的Soundex类型的成员变量soundex
};// 定义一个测试用例,名称为RetainsSoleLetterOfOneLetterWord,使用SoundexEncoding夹具
TEST_F(SoundexEncoding, RetainsSoleLetterOfOneLetterWord) {// 调用soundex实例的encode方法,并将结果保存到变量encoded中auto encoded = soundex.encode("A");// 验证encoded是否与"A000"相等,如果不相等则测试失败ASSERT_THAT(encoded, testing::Eq("A000"));
}// 定义一个测试用例,名称为PadsWithZerosToEnsureThreeDigits,使用SoundexEncoding夹具
TEST_F(SoundexEncoding, PadsWithZerosToEnsureThreeDigits) {// 调用soundex实例的encode方法,并将结果保存到变量encoded中auto encoded = soundex.encode("I");// 验证encoded是否与"I000"相等,如果不相等则测试失败ASSERT_THAT(encoded, testing::Eq("I000"));
}// 主函数
int main() {// 初始化 Google Test 框架testing::InitGoogleTest();// 运行所有测试return RUN_ALL_TESTS();
}

测试的结果都pass,在执行RetainsSoleLetterOfOneLetterWord和PadsWithZerosToEnsureThreeDigits 之前都会创建一个单独的SoundexEncoding 实例,为了自定义Fixture 需要将宏TEST改成TEST_F,其中的F 代表的是“Fixture”,如果没有使用TEST_F 会不能访问soundex 这个成员。
在这里插入图片描述

EXPECT_THAT 宏在断言失败时记录失败,并继续执行测试,因此如果存在多个断言,即使第一个断言失败,其他断言也将被执行。这使您能够查看测试失败时的完整情况。
SSERT_THAT 宏与 EXPECT_THAT 不同,如果断言失败,它将立即将测试标记为失败并停止执行,因此如果存在多个断言,当第一个断言失败时,后续断言将不会执行。
DISABLED_前缀禁用测试


TEST_F(SoundexEncoding, DISABLED_ReplacesMultipleConsonantsWithDigits) {
ASSERT_THAT(soundex.encode("Acdl"), Eq("A234"));
}

gtest_catch_exceptions 出现异常试工具会捕获这个异常,报告测试失败,并会继续运行任何后续测试


[ RUN      ] SoundexEncoding.LimitsLengthToFourCharacters
unknown file: Failure
C++ exception with description "basic_string::_M_create" thrown in the test body.
[  FAILED  ] SoundexEncoding.LimitsLengthToFourCharacters (31465 ms)
[----------] 5 tests from SoundexEncoding (31465 ms total)

Google Mock 会忽略未捕获的异常并继续运行其余的测试。如果您希望在未捕获的异常时终止测试,可以使用以下命令行选项来运行 Google Mock:


--gtest_catch_exceptions=0

Teardown()Test 类的一个虚函数。作为 Google Test 的扩展部分,gMock 的 Test 类提供了额外的特性和语法糖。在使用 gMock 进行单元测试时,可以通过重载 Teardown() 函数来进行某些清理工作。

test double 例子

代码需要安装对应jsoncpp curl库


sudo apt-cache search jsoncpp //如果不知道对应库名字可以执行这个命令查找对应库,如果没找到要去跟新对应的源,sudo apt update
sudo apt-install libjsoncpp-dev 
sudo apt-cache search curl
sudo apt-install libcurl4-gnutls-dev

实现一个从地图上获取对应地址信息的测试。

测试 double HTTP 实现!测试 double 的工作是支持测试的需求。当客户端向 HTTP 对象发送 GET 请求时 test double 可以返回一个预先定义的地图上对应的地址信息响应。
要实现这个下面定义几个class

  1. CurlHttp:使用cURL库进行 HTTP 请求的类。它派生自抽象基类 Http,该类定义了 get() 和 initialize() 两个函数。在调用 get() 函数之前,客户端必须先调用 initialize() 函数进行初始化。

// 使用 libcurl 定义一个具体的 Http 实现类
class CurlHttp : public Http {public:CurlHttp(): curl(NULL){};virtual ~CurlHttp() {curl_global_cleanup();  // 在使用 libcurl 后进行清理}void initialize() {curl_global_init(CURL_GLOBAL_ALL);                                        // 初始化 libcurlcurl = curl_easy_init();                                                  // 创建一个 curl 句柄curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlHttp::writeCallback);  // 设置响应数据回调函数}virtual string get(const string& url) const {response_ = "invalid request";                     // 初始化响应字符串curl_easy_setopt(curl, CURLOPT_URL, url.c_str());  // 设置请求的curl_easy_perform(curl);                           // 发送请求curl_easy_cleanup(curl);                           // 清理 curl 句柄return CurlHttp::Response();  // 返回响应字符串}static string Response() {return response_;}static size_t writeCallback(const char* buf, size_t size, size_t nMemb, void*) {for (auto i = 0u; i < size * nMemb; i++)  // 将响应数据添加到响应字符串中response_.push_back(buf[i]);return size * nMemb;}private:CURL* curl;static string response_;
};
  1. Address: 一个结构体,包含几个字段。地图返回的地址信息。

// 定义一个用于保存 Address 的结构体
struct Address {string road;     // 街道string city;     // 城市string state;    // 州/省string country;  // 国家
};
  1. AddressExtractor:使用JsonCpp库从JSON地址信息字符串中填充Address结构体的类。

// 定义一个用于从 JSON 字符串中提取 Address 的类
class AddressExtractor {public:Address addressFrom(const string& json) const {Address address;Value jsonAddress{jsonAddressFrom(json)};  // 解析 JSON 并提取 address 字段populate(address, jsonAddress);            // 将 JSON 中的各个字段填充到 Address 结构体中return address;}private:Json::Value jsonAddressFrom(const string& json) const {auto location = parse(json);                  // 解析 JSON 字符串return location.get("address", Value::null);  // 返回 JSON 对象中的 address 字段,如果不存在则返回 null}void populate(Address& address, Json::Value& jsonAddress) const {address.road = getString(jsonAddress, "road");  // 分别将 Address 结构体的各个字段填充为 JSON 对象中对应的字段值address.city = getString(jsonAddress, "city");address.state = getString(jsonAddress, "state");address.country = getString(jsonAddress, "country");}Json::Value parse(const string& json) const {Value root;Reader reader;reader.parse(json, root);  // 解析 JSON 字符串并将结果存入 root 变量中return root;}string getString(Json::Value& result, const string& name) const {return result.get(name, "").asString();  // 返回指定字段的字符串值,如果不存在则返回空字符串}
};
  1. PlaceDescriptionService: createGetRequestUrl 函数通过经纬度去,创建get请求。get 通过URL去调用获取对应address 信息。 summaryDescription 通过返回的json 信息解析出对应的地址信息。

// 定义了一个 PlaceDescriptionService 类,提供了获取地点描述信息的服务
class PlaceDescriptionService {public:// 构造函数,初始化 http 对象PlaceDescriptionService(Http* http);// 获取某个经纬度对应地点的简要描述string summaryDescription(const string& latitude, const string& longitude) const {// 根据经纬度组成 GET 请求的 URLauto getRequestUrl = "lat=" + latitude + "&lon=" + longitude;// 发送 GET 请求并获取响应结果auto jsonResponse = http_->get(getRequestUrl);// 从响应结果中提取出地址信息AddressExtractor extractor;auto address = extractor.addressFrom(jsonResponse);// 组合成简要描述并返回return address.road + ", " + address.city + ", " +address.state + ", " + address.country;}private:// 创建 HTTP GET 请求的 URLstring createGetRequestUrl(const string& latitude, const string& longitude) const;// 根据地址信息生成简要描述string summaryDescription(const Address& address) const;Http* http_;  // HTTP 请求处理对象
};

可以实现成为下面的代码


// 创建一个 CurlHttp 类的实例,并调用其 initialize() 函数进行初始化
CurlHttp http;
http.initialize();// 发送 GET 请求,并将响应存储在 jsonResponse 变量中,根据经纬度获取对应的消息
auto jsonResponse = http.get(createGetRequestUrl(latitude, longitude));// 创建一个 AddressExtractor 类的实例,并从 JSON 字符串中提取 Address
AddressExtractor extractor;
auto address = extractor.addressFrom(jsonResponse);// 对提取出的 Address 进行处理,并返回一个字符串作为摘要
return summaryDescription(address);

测试AddressExtractor 地址提取器


// 定义一个测试类 AnAddressExtractor,继承自 testing::Test
class AnAddressExtractor : public testing::Test {public:// 定义一个 AddressExtractor 对象 extractorAddressExtractor extractor;
};// 定义一个参数匹配器 IsEmpty,用于匹配 Address 对象是否为空
MATCHER(IsEmpty, "") {return arg.road.empty() &&   // 道路为空arg.city.empty() &&   // 城市为空arg.state.empty() &&  // 州/省/地区为空arg.country.empty();  // 国家为空
}// 定义测试用例
TEST_F(AnAddressExtractor, ReturnsAnEmptyAddressOnAFailedParse) {// 调用 AddressExtractor 的 addressFrom 方法,传入一个非法的 json 字符串auto address = extractor.addressFrom("not valid json");// 断言:address 对象应该为空ASSERT_THAT(address, IsEmpty());
}// 定义测试用例
TEST_F(AnAddressExtractor, ReturnsPopulatedAddressForValidJsonResult) {// 定义一个 json 字符串auto json = R"({"place_id":"15331615","address":{"road":"War Eagle Court",                 // 道路"city":"Colorado Springs",                // 城市"state":"Colorado",                       // 州/省/地区"country":"United States of America",     // 国家}})";// 调用 AddressExtractor 的 addressFrom 方法,传入 json 字符串auto address = extractor.addressFrom(json);// 断言:Address 对象的属性应该和 json 字符串中的值一致ASSERT_THAT(address.road, testing::Eq("War Eagle Court"));ASSERT_THAT(address.city, testing::Eq("Colorado Springs"));ASSERT_THAT(address.state, testing::Eq("Colorado"));ASSERT_THAT(address.country, testing::Eq("United States of America"));
}

使用Mock 工具进行测试

用测试驱动测试summaryDescription(),模拟HTTP方法get()和initialize()


//http.h
virtual ~Http() {}
virtual void initialize() = 0;
virtual std::string get(const std::string& url) const = 0;

定义派生类


// 定义纯虚函数 get(),用于向指定的 url 发送 get 请求,并返回响应结果
class HttpStub : public Http {public:// 定义纯虚函数 get(),用于向指定的 url 发送 get 请求,并返回响应结果,返回值为 voidMOCK_METHOD0(initialize, void());// 定义一个带有一个参数的 const 成员函数 ,传入参数为 url 字符串,返回值为响应字符串MOCK_CONST_METHOD1(get, string(const string&));
};

Google Mock 为该函数合成一种实现方式以管理与之交互的操作
ValidLatitude和ValidLongitude封装到APlaceDescriptionService的目的是为了方便测试,以及避免代码重复。将这些值封装到测试夹具类中,可以方便地在测试用例中使用,同时也避免了多次重复定义这些常量。此外,这样也使得修改这些常量值时更加方便,只需要在测试夹具类中修改即可。

// 这是一个 PlaceDescriptionService 的测试夹具类定义,继承了 testing::Test,由 Google Test 框架提供
class APlaceDescriptionService : public testing::Test {public:  // 定义静态常量字符串值,表示有效的纬度和经度static const string ValidLatitude;static const string ValidLongitude;
};// 定义静态常量字符串值
const string APlaceDescriptionService::ValidLatitude("38.005");
const string APlaceDescriptionService::ValidLongitude("-104.44");// 定义测试用例方法
// 它属于 APlaceDescriptionService 的测试夹具类
TEST_F(APlaceDescriptionService, MakesHttpRequestToObtainAddress) {// 实例化 HttpStub 对象,用于模拟HttpStub httpStub;// 定义我们期望的 URL 字符串的开头string urlStart{"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"};// 用静态常量值和 httpStub 拼接成期望的 URL 字符串auto expectedURL = urlStart +"lat=" +APlaceDescriptionService::ValidLatitude +"&" +"lon=" +APlaceDescriptionService::ValidLongitude;// 断言 httpStub 的 get 方法将使用期望的 URL 作为参数调用EXPECT_CALL(httpStub, get(expectedURL));// 使用 httpStub 实例化 PlaceDescriptionService 对象PlaceDescriptionService service{&httpStub};// 使用静态常量值调用 PlaceDescriptionService 对象的 summaryDescription 方法string Addressinfo = service.summaryDescription(ValidLatitude, ValidLongitude);
}

EXPECT_CALL(httpStub, get(expectedURL)); 这宏配置Google Mock以验证是否将get()获取得消息发送给httpStub对象。
由于PlaceDescriptionService class 中的 http_->get(getRequestUrl); 的getRequestUrl与期望的expectedURL不一样测试会出现错误


// 获取某个经纬度对应地点的简要描述
string summaryDescription(const string& latitude, const string& longitude) const {// 根据经纬度组成 GET 请求的 URLauto getRequestUrl = "lat=" + latitude + "&lon=" + longitude; // 发送 GET 请求并获取响应结果auto jsonResponse = http_->get(getRequestUrl); //getRequestUrl与期待值不同// 从响应结果中提取出地址信息AddressExtractor extractor;auto address = extractor.addressFrom(jsonResponse);// 组合成简要描述并返回return address.road + ", " + address.city + ", " +address.state + ", " + address.country;
}

在这里插入图片描述

现在重写一下summaryDescription代码


// 获取某个经纬度对应地点的简要描述
string summaryDescription(const string& latitude, const string& longitude) const {string server{"http://open.mapquestapi.com/"};string document{"nominatim/v1/reverse"};string url = server + document + "?" +keyValue("format", "json") + "&" +keyValue("lat", latitude) + "&" +keyValue("lon", longitude);http_->get(url);return "";
}
string keyValue(const string& key,const string& value) const {return key + "=" + value;
}

现在对get() 的返回值进行测试

测试中的EXPECT_CALL()调用告诉Google Mock调用get()时应该返回什么值。测试中的 EXPECT_CALL() 调用告诉 Google Mock 调用get()时应该返回什么值。应该返回一个特定的JSON字符串值。我们不关心 get() 中传递的参数是什么,因为我们已经在 MakesHttpRequestToObtainAddress 中验证了这个行为。我们的 EXPECT_CALL() 使用了通配符匹配器get() 的参数。


// 获取某个经纬度对应地点的简要描述
string summaryDescription(const string& latitude, const string& longitude) const {string server{"http://open.mapquestapi.com/"};string document{"nominatim/v1/reverse"};string url = server + document + "?" +keyValue("format", "json") + "&" +keyValue("lat", latitude) + "&" +keyValue("lon", longitude);//添加下面代码对get函数的返回值进行测试auto response = http_->get(url);AddressExtractor extractor;auto address = extractor.addressFrom(response);return address.summaryDescription();
}

编写测试的代码


// TEST_F 宏用来定义一个测试用例,并使用 APlaceDescriptionService 类作为测试的 Fixture
TEST_F(APlaceDescriptionService, FormatsRetrievedAddressIntoSummaryDescription) {// 创建 HttpStub 对象,并设置 EXPECT_CALL,告诉 Google Mock 当调用 get() 时应该返回什么值。HttpStub httpStub;EXPECT_CALL(httpStub, get(testing::_)).WillOnce(testing::Return(R"({ "address": {"road":"Drury Ln","city":"Fountain","state":"CO","country":"US" }})"));// 用 HttpStub 对象创建 PlaceDescriptionService 对象。PlaceDescriptionService service(&httpStub);// 调用 service.summaryDescription(),获取经纬度对应的地址简述信息。auto description = service.summaryDescription(ValidLatitude, ValidLongitude);// 将获取到的地址简述信息与期望值 "Drury Ln, Fountain, CO, US" 进行比较。ASSERT_THAT(description, testing::Eq("Drury Ln, Fountain, CO, US"));
}

指定get 的返回值然后通过ASSERT_THAT 进行判断。

对initialize 函数测试

对initialize 函数测试,在MakesHttpRequestToObtainAddress,中的EXPECT_CALL(httpStub, get(expectedURL)); 之前添加一行EXPECT_CALL(httpStub, initialize()); 确保在测试 http_stub.get(address) 方法之前,httpStub.initialize() 方法被调用了


// 定义测试用例方法
// 它属于 APlaceDescriptionService 的测试夹具类
TEST_F(APlaceDescriptionService, MakesHttpRequestToObtainAddress) {// 实例化 HttpStub 对象,用于模拟HttpStub httpStub;// 定义我们期望的 URL 字符串的开头string urlStart{"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"};// 用静态常量值和 httpStub 拼接成期望的 URL 字符串auto expectedURL = urlStart +"lat=" +APlaceDescriptionService::ValidLatitude +"&" +"lon=" +APlaceDescriptionService::ValidLongitude;EXPECT_CALL(httpStub, initialize());// 断言 httpStub 的 get 方法将使用期望的 URL 作为参数调用EXPECT_CALL(httpStub, get(expectedURL));// 使用 httpStub 实例化 PlaceDescriptionService 对象PlaceDescriptionService service{&httpStub};// 使用静态常量值调用 PlaceDescriptionService 对象的 summaryDescription 方法string addressinfo1 = service.summaryDescription(ValidLatitude, ValidLongitude);cout << addressinfo1 << endl;
}

在这里插入图片描述

出现这个问题我们需要在PlaceDescriptionService 中的summaryDescription 函数中auto response = http_->get(url); 之前对initialize() 进行调用


string summaryDescription(const string& latitude, const string& longitude) const {string server{"http://open.mapquestapi.com/"};string document{"nominatim/v1/reverse"};string url = server + document + "?" +keyValue("format", "json") + "&" +keyValue("lat", latitude) + "&" +keyValue("lon", longitude);http_->initialize();auto response = http_->get(url);AddressExtractor extractor;auto address = extractor.addressFrom(response);return address.summaryDescription();
}

mock中的顺序

当在测试代码中使用 Mock 对象时,可以使用 EXPECT_CALL 宏来设置期望的函数调用,以及配置 Mock 对象的行为。这些期望的函数调用会被存储在 Mock 对象中,并按照它们被创建的顺序保存在 Mock 对象中。这样,当测试运行时,Mock 对象会按照这个顺序检查函数调用,以确保它们按照预期执行。
所以,当使用 Mock 对象时,期望函数调用的顺序可能非常重要,因为 Mock 对象会按顺序检查它们是否被正确调用。如果期望函数调用的顺序不正确,Mock 对象可能会抛出错误或失败,或测试用例可能不能正确地检查期望的行为。
为了解决这个问题,Google Test 提供了 ON_CALL 和 EXPECT_CALL 宏,以便在 Mock 对象中为不同的函数调用设置期望,并确保它们按照正确的顺序执行。可以使用 EXPECT_CALL 宏来设置常规的函数调用,以及期望的执行顺序,还可以使用 ON_CALL 宏来设置其他的函数调用,以特定的条件执行。这些宏可以帮助测试代码更加灵活和高效的设置 Mock 对象和检查它们的行为。

如果我们无意中交换了 Http 类中 initialize() 和 get() 函数的调用顺序,这种情况可能会发生。


string summaryDescription(const string& latitude, const string& longitude) const {string server{"http://open.mapquestapi.com/"};string document{"nominatim/v1/reverse"};string url = server + document + "?" +keyValue("format", "json") + "&" +keyValue("lat", latitude) + "&" +keyValue("lon", longitude);//get 和initialize() 函数的调用顺序被交换auto response = http_->get(url); http_->initialize();AddressExtractor extractor;auto address = extractor.addressFrom(response);return address.summaryDescription();
}

令人惊讶的是,尽管我们交换了 Http 类中 initialize() 和 get() 函数的调用顺序,但测试仍然通过了!Google Mock 默认并不关心期望的的匹配顺序。如果你关心顺序,你可以告诉 Google Mock(以及其他许多 C++ mocking 工具)来验证它。最简单的方法是在测试的顶部声明一个 InSequence 实例,并确保后续的 EXPECT_CALL 函数出现的顺序符合你的期望。

// 定义测试用例方法
// 它属于 APlaceDescriptionService 的测试夹具类
TEST_F(APlaceDescriptionService, MakesHttpRequestToObtainAddress) {//testing::InSequence 所有的调用都都需要按照严格EXPECT_CALL的顺序执行 并且如果有调用顺序不正确的情况,测试将会失败。testing::InSequence forceExpectationOrder;// 实例化 HttpStub 对象,用于模拟HttpStub httpStub;// 定义我们期望的 URL 字符串的开头string urlStart{"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"};// 用静态常量值和 httpStub 拼接成期望的 URL 字符串auto expectedURL = urlStart +"lat=" +APlaceDescriptionService::ValidLatitude +"&" +"lon=" +APlaceDescriptionService::ValidLongitude;EXPECT_CALL(httpStub, initialize());// 断言 httpStub 的 get 方法将使用期望的 URL 作为参数调用EXPECT_CALL(httpStub, get(expectedURL));// 使用 httpStub 实例化 PlaceDescriptionService 对象PlaceDescriptionService service{&httpStub};// 使用静态常量值调用 PlaceDescriptionService 对象的 summaryDescription 方法string addressinfo1 = service.summaryDescription(ValidLatitude, ValidLongitude);cout << addressinfo1 << endl;
}

在这里插入图片描述

EXPECT_CALL

EXPECT_CALL 宏支持许多附加修饰符。这是它的语法:


EXPECT_CALL(mock_object, method(matchers)).With(multi_argument_matcher) ?.Times(cardinality) ?.InSequence(sequences) *.After(expectations) *.WillOnce(action) *.WillRepeatedly(action) ?.RetiresOnSaturation(); ?

最有用的修饰符可能是 Times(),它让你可以指定一个方法被调用的次数。即使你知道一个方法会被调用多次,但不知道具体多少次,那么你也可以使用WillRepeatedly()。下面示例中的接口是在DifficultCollaborator类中定义的,需要一个int* 参数,和一个bool 的返回值


virtual bool calculate(int* result);

你可以使用 WillOnce() 或 WillRepeatedly() 为 Google Mock 的期望指定一个操作。大多数情况下,该操作将是返回一个值。如果是需要进行更复杂操作的函数,你可以使用 DoAll(),它可以创建一个由两个或更多操作组成的复合操作。


#include <gmock/gmock.h>// 定义 DifficultCollaborator 类,它是一个被测试对象的依赖关系
class DifficultCollaborator {public:virtual bool calculate(int* result) {// Do some difficult calculation*result = 0;return false;}
};// 定义目标类 Target,它是我们需要进行测试的类
class Target {public:int execute(DifficultCollaborator* collaborator) {int result;if (collaborator->calculate(&result)) {return result;}return -1;}
};// 定义 DifficultCollaborator 类的 Mock 对象 DifficultCollaboratorMock,用于模拟 DifficultCollaborator 类的行为
class DifficultCollaboratorMock : public DifficultCollaborator {public:MOCK_METHOD(bool, calculate, (int* result), (override));
};// 定义测试用例 TargetTest,测试 Target 类的行为
TEST(TargetTest, executeTest) {// 创建 DifficultCollaboratorMock 实例DifficultCollaboratorMock difficult;// 创建 Target 类实例Target calc;// 断言 DifficultCollaboratorMock 实例中的 calculate 方法应该被调用,// 并指定此调用的期望行为:将输出参数设置为 3,并返回 trueEXPECT_CALL(difficult, calculate(testing::_)).WillOnce(testing::DoAll(testing::SetArgPointee<0>(3),testing::Return(true)));// 调用 Target 类的 execute 方法,将 DifficultCollaboratorMock 实例传入,并将返回值保存到 result 变量中auto result = calc.execute(&difficult);// 断言返回的结果是否等于 3ASSERT_THAT(result, testing::Eq(3));
}// main 函数,运行测试用例
int main(int argc, char** argv) {testing::InitGoogleMock(&argc, argv);return RUN_ALL_TESTS();
}

Getting Test Doubles in Place

引入test double时,你有两个任务。首先,编写test double。其次,让目标使用它的一个实例。这样做被称为依赖注入(dependency injection, DI)。

依赖注入,MyApp 提供依赖的接口,外面实现好对象把依赖的对象传进去

#include <iostream>
#include <vector>// 定义数据处理接口
class IDataProcessor {public:virtual std::vector<int> processData(const std::vector<int>& data) = 0;// 定义数据处理方法
};// 实现数据处理接口的类
class DataProcessor : public IDataProcessor {public:std::vector<int> processData(const std::vector<int>& data) override {// 实现数据处理方法std::vector<int> result;for (auto x : data) {result.push_back(x * x);}return result;}
};// 依赖注入的类
class MyApp {public:MyApp(IDataProcessor* dataProcessor): m_dataProcessor(dataProcessor) {}// 使用构造函数注入IDataProcessor实例void run() {  // 执行数据处理流程的方法std::vector<int> data = {1, 2, 3, 4, 5};std::vector<int> result = m_dataProcessor->processData(data);// 使用注入的IDataProcessor进行数据处理std::cout << "Processed data:\n";for (auto x : result) {std::cout << x << " ";}std::cout << std::endl;}private:IDataProcessor* m_dataProcessor;  // 保存注入的IDataProcessor实例
};int main() {DataProcessor dataProcessor;  // 创建一个实现IDataProcessor接口的类MyApp app(&dataProcessor);    // 创建MyApp实例,并注入IDataProcessor实例app.run();                    // 执行数据处理流程return 0;
}

在PlaceDescriptionService的例子中,我们通过构造函数注入了测试替身。在某些情况下,你可能会发现更适合使用setter成员函数来传递测试double。这些注入测试替身的方法被称为构造函数注入或setter注入。

Override Factory Method and Override Getter

工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法将一个类的实例化延迟到其子类。 对每一个子类产品都分别对应一个工厂子类,用来创建相应的产品,若增加了新的产品,只需相应增加工厂子类即可。


#include <iostream>// 工厂方法模式中的产品类
class Product {public:virtual void use() = 0;
};// 具体的产品类A
class ProductA : public Product {public:void use() override {std::cout << "Product A used" << std::endl;}
};// 具体的产品类B
class ProductB : public Product {public:void use() override {std::cout << "Product B used" << std::endl;}
};// 抽象工厂类
class Factory {public:virtual Product* createProduct() = 0;
};// 具体工厂类A,用于创建ProductA类的实例
class FactoryA : public Factory {public:Product* createProduct() override {return new ProductA();}
};// 具体工厂类B,用于创建ProductB类的实例
class FactoryB : public Factory {public:Product* createProduct() override {return new ProductB();}
};int main() {// 使用具体的工厂类A创建一个ProductA实例FactoryA factoryA;Product* productA = factoryA.createProduct();productA->use();// 使用具体的工厂类B创建一个ProductB实例FactoryB factoryB;Product* productB = factoryB.createProduct();productB->use();delete productA;delete productB;return 0;
}

现在回到之前的代码


// 包含必要的头文件
#include <curl/curl.h>            // 用于 HTTP 请求
#include <jsoncpp/json/reader.h>  // 用于 JSON 解析
#include <jsoncpp/json/value.h>
#include <string>
#include "gmock/gmock.h"  // 用于单元测试
#include "gtest/gtest.h"using namespace std;
using namespace Json;// 定义一个用于保存 Address 的结构体
struct Address {string road;     // 街道string city;     // 城市string state;    // 州/省string country;  // 国家std::string summaryDescription() const {return road + ", " + city + ", " + state + ", " + country;}
};// 定义一个 HTTP 接口
class Http {public:virtual ~Http() {}// 定义纯虚函数 initialize(),用于初始化 Http 对象virtual void initialize() = 0;// 定义纯虚函数 get(),用于向指定的 url 发送 get 请求,并返回响应结果virtual string get(const string& url) const = 0;
};// 定义纯虚函数 get(),用于向指定的 url 发送 get 请求,并返回响应结果
class HttpStub : public Http {public:// 定义纯虚函数 get(),用于向指定的 url 发送 get 请求,并返回响应结果,返回值为 voidMOCK_METHOD0(initialize, void());// 定义一个带有一个参数的 const 成员函数 ,传入参数为 url 字符串,返回值为响应字符串MOCK_CONST_METHOD1(get, string(const string&));
};// 使用 libcurl 定义一个具体的 Http 实现类
class CurlHttp : public Http {public:CurlHttp(): curl(NULL){};virtual ~CurlHttp() {curl_global_cleanup();  // 在使用 libcurl 后进行清理}void initialize() {curl_global_init(CURL_GLOBAL_ALL);                                        // 初始化 libcurlcurl = curl_easy_init();                                                  // 创建一个 curl 句柄curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlHttp::writeCallback);  // 设置响应数据回调函数}virtual string get(const string& url) const {response_ = "invalid request";                     // 初始化响应字符串curl_easy_setopt(curl, CURLOPT_URL, url.c_str());  // 设置请求的curl_easy_perform(curl);                           // 发送请求curl_easy_cleanup(curl);                           // 清理 curl 句柄return CurlHttp::Response();  // 返回响应字符串}static string Response() {return response_;}static size_t writeCallback(const char* buf, size_t size, size_t nMemb, void*) {for (auto i = 0u; i < size * nMemb; i++)  // 将响应数据添加到响应字符串中response_.push_back(buf[i]);return size * nMemb;}private:CURL* curl;static string response_;
};// 定义一个用于从 JSON 字符串中提取 Address 的类
class AddressExtractor {public:Address addressFrom(const string& json) const {Address address;Value jsonAddress{jsonAddressFrom(json)};  // 解析 JSON 并提取 address 字段populate(address, jsonAddress);            // 将 JSON 中的各个字段填充到 Address 结构体中return address;}private:Json::Value jsonAddressFrom(const string& json) const {auto location = parse(json);                  // 解析 JSON 字符串return location.get("address", Value::null);  // 返回 JSON 对象中的 address 字段,如果不存在则返回 null}void populate(Address& address, Json::Value& jsonAddress) const {address.road = getString(jsonAddress, "road");  // 分别将 Address 结构体的各个字段填充为 JSON 对象中对应的字段值address.city = getString(jsonAddress, "city");address.state = getString(jsonAddress, "state");address.country = getString(jsonAddress, "country");}Json::Value parse(const string& json) const {Value root;Reader reader;reader.parse(json, root);  // 解析 JSON 字符串并将结果存入 root 变量中return root;}string getString(Json::Value& result, const string& name) const {return result.get(name, "").asString();  // 返回指定字段的字符串值,如果不存在则返回空字符串}
};// 定义了一个 PlaceDescriptionService 类,提供了获取地点描述信息的服务
class PlaceDescriptionService {public:// 构造函数,初始化 http 对象PlaceDescriptionService(Http* http): http_(http){};// 获取某个经纬度对应地点的简要描述string summaryDescription(const string& latitude, const string& longitude) const {string server{"http://open.mapquestapi.com/"};string document{"nominatim/v1/reverse"};string url = server + document + "?" +keyValue("format", "json") + "&" +keyValue("lat", latitude) + "&" +keyValue("lon", longitude);http_->initialize();auto response = http_->get(url);AddressExtractor extractor;auto address = extractor.addressFrom(response);return address.summaryDescription();}string keyValue(const string& key,const string& value) const {return key + "=" + value;}private:// 创建 HTTP GET 请求的 URLstring createGetRequestUrl(const string& latitude, const string& longitude) const;// 根据地址信息生成简要描述string summaryDescription(const Address& address) const;Http* http_;  // HTTP 请求处理对象
};// 这是一个 PlaceDescriptionService 的测试夹具类定义,继承了 testing::Test,由 Google Test 框架提供
class APlaceDescriptionService : public testing::Test {public:  // 定义静态常量字符串值,表示有效的纬度和经度static const string ValidLatitude;static const string ValidLongitude;
};// 定义静态常量字符串值
const string APlaceDescriptionService::ValidLatitude("38.005");
const string APlaceDescriptionService::ValidLongitude("-104.44");// 定义测试用例方法
// 它属于 APlaceDescriptionService 的测试夹具类
TEST_F(APlaceDescriptionService, MakesHttpRequestToObtainAddress) {testing::InSequence forceExpectationOrder;// 实例化 HttpStub 对象,用于模拟HttpStub httpStub;// 定义我们期望的 URL 字符串的开头string urlStart{"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"};// 用静态常量值和 httpStub 拼接成期望的 URL 字符串auto expectedURL = urlStart +"lat=" +APlaceDescriptionService::ValidLatitude +"&" +"lon=" +APlaceDescriptionService::ValidLongitude;EXPECT_CALL(httpStub, initialize());// 断言 httpStub 的 get 方法将使用期望的 URL 作为参数调用EXPECT_CALL(httpStub, get(expectedURL));// 使用 httpStub 实例化 PlaceDescriptionService 对象PlaceDescriptionService service{&httpStub};// 使用静态常量值调用 PlaceDescriptionService 对象的 summaryDescription 方法string addressinfo1 = service.summaryDescription(ValidLatitude, ValidLongitude);cout << addressinfo1 << endl;
}// TEST_F 宏用来定义一个测试用例,并使用 APlaceDescriptionService 类作为测试的 Fixture
TEST_F(APlaceDescriptionService, FormatsRetrievedAddressIntoSummaryDescription) {// 创建 HttpStub 对象,并设置 EXPECT_CALL,告诉 Google Mock 当调用 get() 时应该返回什么值。HttpStub httpStub;EXPECT_CALL(httpStub, get(testing::_)).WillOnce(testing::Return(R"({ "address": {"road":"Drury Ln","city":"Fountain","state":"CO","country":"US" }})"));// 用 HttpStub 对象创建 PlaceDescriptionService 对象。PlaceDescriptionService service(&httpStub);// 调用 service.summaryDescription(),获取经纬度对应的地址简述信息。auto description = service.summaryDescription(ValidLatitude, ValidLongitude);// 将获取到的地址简述信息与期望值 "Drury Ln, Fountain, CO, US" 进行比较。ASSERT_THAT(description, testing::Eq("Drury Ln, Fountain, CO, US"));
}// 定义 main 函数
int main(int argc, char** argv) {testing::InitGoogleMock(&argc, argv);  // 初始化 Google Mock 并运行所有测试return RUN_ALL_TESTS();
}

还有其他获得测试替身的技术。使用最适合你的情况的一个。要应用覆盖工厂方法,您必须更改生产代码,以便在任何需要collaborator实例的时候使用工厂方法。下面是在PlaceDescriptionService中实现更改的一种方法


http://chatgpt.dhexx.cn/article/CeWXxPXg.shtml

相关文章

TDD (test driver development)测试驱动开发

##为什么需要测试驱动/或者说需要单元测试 我们工作接触的软件项目&#xff0c;不是学生时代&#xff0c;玩一玩就不管了&#xff0c;工作的项目&#xff0c;需要长期维护&#xff0c;并且随着时间的推移需要增加新的需求&#xff0c;进行修改&#xff0c;优化。此时已经距离你…

tdd(测试驱动开发)的概述

最近的工作的项目&#xff0c;使用了tdd&#xff08;test-driven development测试驱动开发&#xff09;的开发模式。 这两几年大概听说了无数种xxx-dd, ddd, tdd, atdd, bdd, fdd, udd各种名词眼花缭乱&#xff0c;当然很多dd其实也有相互借鉴&#xff08;抄袭&#xff09;的部…

测试驱动开发(TDD)实践与技巧

文章目录 引言Google Mock测试用例结构断言经典式断言Hamcrest 断言 测试驱动开发&#xff1a;第一个示例开场白开始吧去掉不干净的代码增量性fixture 设置思索与测试驱动开发测试驱动与测试 测试驱动开发基础与单元测试单元测试的组织结构测试驱动开发周期&#xff1a;红-绿-重…

opencv配置相关的截图参考

opencv配置相关的截图参考&#xff0c;如下&#xff1a;

Anaconda3安装及opencv配置

一、Anaconda安装 1.直接百度搜anaconda&#xff0c;进入官网即可&#xff08;anaconda网站链接&#xff09; 2.点击图片上黑框&#xff08;Get Started&#xff09;即可进入下一步&#xff0c;选择下图中第四个 3.选择适合电脑类型的anaconda安装器&#xff0c;注意选好64位…

linux安装配置opencv

刚开始学习ubuntu&#xff0c;有些项目需要用到opencv&#xff0c;当我用下面的命令安装包的时候&#xff0c;总是出现“E&#xff1a;无法定位软件包 opencv”的错误。然后开始着手解决&#xff0c;网上搜集了很多的教程&#xff0c;大部分都是说要更换源&#xff0c;我也照做…

opencv安装配置测试

前面安装了pcl和qt&#xff0c;以及qt中的vtk&#xff0c;这里配置下opencv4.3 将cv复制到D盘下。 安装完成之后&#xff0c;添加环境变量&#xff0c;[计算机]->右键 [属性]->[高级系统设置]->[环境变量]->[系统环境变量]->编辑 [Path]&#xff0c;添加“D:…

Qt中配置OpenCV

Qt中配置OpenCV 1. 环境下载2. 进行编译和安装2.1 新建opencv-build文件夹&#xff0c;用于opencv的 编译和安装&#xff08;直接在opencv下新建即可&#xff09;2.2 CMake设置2.3 命令行进行编译安装 3. Qt测试4. 其它问题4.1 若电脑上安装有PyQt4.2 电脑无法访问wai网 1. 环境…

Anaconda配置OpenCV

文章目录 1.安装Anaconda2.配置OpenCV2.1打开Anaconda Prompt2.2找到里面Scripts的路径2.3下载2.4验证是否配置成功 3.出错解决办法4.参考文章 1.安装Anaconda 可以查看我的上一篇文章&#xff1a;Anaconda下载、安装和环境配置 2.配置OpenCV 2.1打开Anaconda Prompt 在开始…

Opencv学习笔记——opencv配置安装与IDE环境安装

文章目录 前言一、opencv配置安装二、IDE的安装总结 前言 既然开始对AI视觉这个方面有兴趣&#xff0c;也初步接触了一些AI视觉在嵌入式方面的应用&#xff0c;那自然少不了对Opencv的学习。到现在开始学习opencv之前对它了解不多&#xff0c;只知道opencv的全称是Open Source…

vs + python + opencv 配置

首要条件&#xff0c;在vs上安装python环境。 以Visual Studio 2019为例讲解如何配置python、opencv、及相关第三方库。&#xff08;其它vs版本只是在界面上有所区别&#xff0c;过程相同。&#xff09; 步骤一&#xff1a;安装python开发工具 按下图操作&#xff0c;勾选Pyt…

vscode配置opencv

前言 本篇文章主要用来记录使用vscode配置opencv的全过程&#xff0c;在整个过程中需要用到的工具包括vscode安装包、MinGW-w64和opencv的源码。vs studio配置opencv比较简单&#xff0c;opencv官网中已经有用vs studio编译器编译好的opencv库&#xff0c;但是对于vscode而言&a…

windows下 C++ openCV配置及x86编译(傻瓜式教程)

本傻瓜教程需要的环境如下: IDE: vs2015或vs2017 , windows 10 或 11 vs2017下载地址如下: ①百度网盘 链接&#xff1a;https://pan.baidu.com/s/1r628e9M5lv_F9IWO-h05jA 提取码&#xff1a;23a7 ②官网地址 https://my.visualstudio.com/Downloads/Featured?mktzh-cnh…

【Qt+OpenCV配置简介】

【Qt&OpenCV】QtOpenCV配置简介 文章目录 【Qt&OpenCV】QtOpenCV配置简介前言一、Qt安装二、OpenCV安装三、Qt配置OpenCV四、测试​总结 前言 越来越多的开发人员选择基于开源的Qt框架与OpenCV来实现界面和算法&#xff0c;其原因不单单是无版权问题&#xff0c;更多是…

Qt+OpenCV配置教程(图解亲测)

文章目录 QtOpenCV配置教程安装配置使用 QtOpenCV配置教程 安装 我都安的最新版的&#xff08;cmake 3.22.1 opencv 4.5.1 qt 5.12.1&#xff09;。 1、cmake安装 2、qt mingw安装 3、opencv安装 配置 首先要设置环境变量。 换成对应你自己版本、位置的环境变量 D:\Prog…

Python配置OpenCV

pip install opencv-pyton&#xff0c;但是由于网络原因&#xff0c;会导致下载不成功。可以从这个网站下载python版本对应的opencv安装包&#xff1a;https://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv 比如我的python版本是3.8.8 > 对应python版本&#xff0c;下载这个…

Opencv环境配置

下载 可以在Opencv官网下载发布包&#xff0c;opencv4需要C版本高&#xff0c;可以选择opencv3。 我这里就下载Opencv3.4.14的windows版本&#xff0c;注意这里下载后里面包含源码和编译后的lib和dll。所以不用单独下载Sources。 下载后解压出来就是这样&#xff1a; 创建项目…

opencv配置VS2019环境

首先下载opencv opencv下载网址 1.VS2019安装插件 打开以后根据需要选择工作负载&#xff0c;如果进行C/C开发的话&#xff0c;只需要选择 使用c的桌面开发即可 接着点击“单个组件”按钮&#xff0c;可以看到很多组件如下图所示&#xff0c;选择自己需要的组件即可&#xff…

Visual Studio + Opencv配置

目录 Opencv安装、环境变量配置Visual Studio 库的两种配置 本文使用操作系统为Windows10&#xff0c;仅适用于windows环境Visual Studio编译器上的 Opencv开发配置。Opencv所有平台通用的配置方式是从官网下载源码&#xff0c;然后使用CMake编译成对应平台的库&#xff0c;比…

【环境配置】Visual Studio opencv配置

需求 在Visual Studio环境中编写C代码&#xff0c;同时可以调用OpenCV的相关代码。 1.安装OpenCV 访问 opencv 官网下载对应平台的库文件。 注意&#xff1a;Visual Studio和OpenCV有相应的版本对应&#xff0c;本人使用的是VS2013&#xff0c;而Visual Studio2013对应的是v…