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) {
// 定义期望的URL
std::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) {
// 调用外部依赖 PaymentGateway
return 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);
// 验证购物车应该返回 false
if (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); // 初始化 libcurl
curl = 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 请求的 URL
auto 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 请求的 URL
string 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 对象 extractor
AddressExtractor 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 请求,并返回响应结果,返回值为 void
MOCK_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 请求的 URL
auto 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,并返回 true
EXPECT_CALL(difficult, calculate(testing::_))
.WillOnce(testing::DoAll(
testing::SetArgPointee<0>(3),
testing::Return(true)));
// 调用 Target 类的 execute 方法,将 DifficultCollaboratorMock 实例传入,并将返回值保存到 result 变量中
auto result = calc.execute(&difficult);
// 断言返回的结果是否等于 3
ASSERT_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 请求,并返回响应结果,返回值为 void
MOCK_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); // 初始化 libcurl
curl = 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 请求的 URL
string 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中实现更改的一种方法