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 的三条规则是:
- 在编写新功能之前,先编写测试。
- 仅编写足以使测试 Pass 的代码。当测试已经通过时,不要写更多的代码。
- 重构现有的代码以消除重复和提高可读性
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
用法:
- Match 期望的参数值 可以使用MATCHER宏定义一个匹配器,用于期望特定的参数值。例如
MATCHER_P(IsEqualToString, expected, "") { return arg == expected; }
EXPECT_CALL(mockObject, someMethod(IsEqualToString("expectedValue")));
在这个例子中,我们定义了一个IsEqualToString匹配器,用于期望someMethod方法的参数等于"expectedValue"。然后,我们将这个期望传递给EXPECT_CALL宏。
- Times 期望方法调用的次数 可以使用Times宏指定期望方法被调用的精确次数,例如:
EXPECT_CALL(mockObject, someMethod()).Times(3);
在这个例子中,我们期望someMethod方法被调用3次
- WillOnce / WillRepeatedly 模拟返回值可以使用WillOnce宏指定一次期望调用的返回值,例如:
EXPECT_CALL(mockObject, someMethod()).WillOnce(Return(1));
在这个例子中,我们期望someMethod方法被调用一次,并返回1。可以使用WillRepeatedly宏指定多次期望调用的返回值,例如:
EXPECT_CALL(mockObject, someMethod()).WillRepeatedly(Return(1));
在这个例子中,我们期望someMethod方法被多次调用,并返回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
- 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_;
};
- Address: 一个结构体,包含几个字段。地图返回的地址信息。
// 定义一个用于保存 Address 的结构体
struct Address {string road; // 街道string city; // 城市string state; // 州/省string country; // 国家
};
- 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(); // 返回指定字段的字符串值,如果不存在则返回空字符串}
};
- 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中实现更改的一种方法