Pact Consumer Swift
ℹ
新增功能包括Pact Specification v3,简化了安装流程和更好地管理模拟服务器进程,当前正在积极开发中,可在PactSwift找到。我们目前正在寻找试用并反馈的人。
此库为创建消费者Pacts提供了Swift/Objective C DSL。它支持在基于HTTP(以及某些实现中的消息队列)的集成间进行依赖系统之间的消费者驱动合同测试。
为什么需要它? 为了测试您的应用程序和服务之间的通信边界。您可以在此了解如何在移动环境中使用Pact: Yow! Connected 2016 Andrew Spinks - 增加您服务集成的信心。
实现了Pact Specification v2,包括灵活匹配。
此DSL依赖于Ruby的pact-ruby-standalone(brew tap)来为测试提供模拟服务。
安装
注意:关于从0.2到0.3的更新,请参阅升级部分。
安装 Pact Mock Service
Homebrew
brew tap pact-foundation/pact-ruby-standalone
brew install pact-ruby-standalone
以下工具将会被安装:
pact
pact-broker
pact-message
pact-mock-service
pact-provider-verifier
pact-publish
pact-stub-service
手动安装
或者,您可以为您所在的平台下载并安装pact-ruby-standalone存档,并按照写在Pact Ruby Standalone 版本说明中的安装说明进行安装。
Xcode 设置
在 Xcode 中,编辑您的方案并添加针对 Test
的 pre- 和 post-actions 以启动和停止 pact-mock-service
。确保您在选择下拉菜单中选择了您的目标 Provide build settings from。
# Pre-actions
PATH=/path/to/your/standalone/pact/bin:$PATH
pact-mock-service start --pact-specification-version 2.0.0 --log "${SRCROOT}/tmp/pact.log" --pact-dir "${SRCROOT}/tmp/pacts" -p 1234
# Post-actions
PATH=/path/to/your/standalone/pact/bin:$PATH
pact-mock-service stop
注意:您的生成的 Pact 文件将存放在 "${SRCROOT}/tmp/pacts"
文件夹中。
将 PactConsumerSwift 库添加到您的项目中
Carthage
使用- 查看使用 Carthage 对 iOS 目标使用
pact-consumer-swift
的示例项目,请参阅 PactSwiftExample。
- 查看使用 Carthage 对 macOS 目标通过
pact-consumer-swift
的示例项目,请参阅 PactMacOSExample。
CocoaPods
使用- 查看使用 CocoaPods 对 iOS 目标使用
pact-consumer-swift
的示例项目,请参阅 PactObjectiveCExample。
Swift Package Manager
使用- 查看使用 Swift Package Manager 对终端运行的程序使用
pact-consumer-swift
库的示例项目,请参阅 PactSwiftPMExample。
编写 Pact 测试
用 Swift 进行测试
编写与以下类似的单元测试(注:此示例使用 Quick 测试框架)
import PactConsumerSwift
...
beforeEach {
animalMockService = MockService(provider: "Animal Service", consumer: "Animal Consumer Swift")
animalServiceClient = AnimalServiceClient(baseUrl: animalMockService!.baseUrl)
}
it("gets an alligator") {
animalMockService!.given("an alligator exists")
.uponReceiving("a request for an alligator")
.withRequest(method:.GET, path: "/alligator")
.willRespondWith(status:200,
headers: ["Content-Type": "application/json"],
body: ["name": "Mary"])
//Run the tests
animalMockService!.run { (testComplete) -> Void in
animalServiceClient!.getAlligator { (alligator) in
expect(alligator.name).to(equal("Mary"))
testComplete()
}
}
}
在运行函数中可以包含一个可选的 timeout
(秒)参数。默认值为30秒。
...
animalMockService!.run(timeout: 60) { (testComplete) -> Void in
animalServiceClient!.getAlligator { (alligator) in
expect(alligator.name).to(equal("Mary"))
testComplete()
}
}
使用Objective-C进行测试
编写与以下类似的单元测试
@import PactConsumerSwift;
...
- (void)setUp {
[super setUp];
self.animalMockService = [[MockService alloc] initWithProvider:@"Animal Provider"
consumer:@"Animal Service Client Objective-C"];
self.animalServiceClient = [[OCAnimalServiceClient alloc] initWithBaseUrl:self.animalMockService.baseUrl];
}
- (void)testGetAlligator {
typedef void (^CompleteBlock)();
[[[[self.animalMockService given:@"an alligator exists"]
uponReceiving:@"oc a request for an alligator"]
withRequestHTTPMethod:PactHTTPMethodGET
path:@"/alligator"
query:nil headers:nil body:nil]
willRespondWithHTTPStatus:200
headers:@{@"Content-Type": @"application/json"}
body: @"{ \"name\": \"Mary\"}" ];
[self.animalMockService run:^(CompleteBlock testComplete) {
Animal *animal = [self.animalServiceClient getAlligator];
XCTAssertEqualObjects(animal.name, @"Mary");
testComplete();
}];
}
在运行函数中可以包含一个可选的 timeout
(秒)参数。默认值为30秒。
...
[self.animalMockService run:^(CompleteBlock testComplete) {
Animal *animal = [self.animalServiceClient getAlligator];
XCTAssertEqualObjects(animal.name, @"Mary");
testComplete();
} timeout:60];
}
使用XCTest进行测试
编写与以下类似的单元测试
import PactConsumerSwift
...
var animalMockService: MockService?
var animalServiceClient: AnimalServiceClient?
override func setUp() {
super.setUp()
animalMockService = MockService(provider: "Animal Provider", consumer: "Animal Service Client")
animalServiceClient = AnimalServiceClient(baseUrl: animalMockService!.baseUrl)
}
func testItGetsAlligator() {
// Prepare the expecated behaviour using pact's MockService
animalMockService!
.given("an alligator exists")
.uponReceiving("a request for alligator")
.withRequest(method: .GET, path: "/alligator")
.willRespondWith(status: 200,
headers: ["Content-Type": "application/json"],
body: [ "name": "Mary" ])
// Run the test
animalMockService!.run(timeout: 60) { (testComplete) -> Void in
self.animalServiceClient!.getAlligator { (response) -> in
XCTAssertEqual(response.name, "Mary")
testComplete()
}
}
}
...
在运行函数中可以包含一个可选的 timeout
(秒)参数。默认值为30秒。
...
// Run the test
animalMockService!.run(timeout: 60) { (testComplete) -> Void in
self.animalServiceClient!.getAlligator { (response) -> in
XCTAssertEqual(response.name, "Mary")
testComplete()
}
}
关于如何测试 https
的示例,请参阅 PactSSLSpec.swift。
匹配
除了逐字匹配之外,在 Matcher
类中还有3个有用的匹配函数,可以增加表达性并减少脆弱的测试案例。
Matcher.term(matcher, generate)
- 告诉Pact使用给定的正则表达式来匹配值,在模拟响应中用generate
。generate
必须是一个字符串。Matcher.somethingLike(content)
- 告诉Pact值本身并不重要,只要元素 类型(有效的JSON数字、字符串、对象等)本身匹配。Matcher.eachLike(content, min)
- 告诉Pact值应该是数组类型,由传入的类似元素组成。min
必须大于等于1。content
可以是有效的JSON值:例如字符串、数字和对象。
注意:需要注意的一个限制是,您将需要使用有效的Ruby 正则表达式,并使用双反斜杠进行转义。
请参阅 PactSpecs.swift
、 PactObjectiveCTests.m
以了解如何期待错误响应、如何使用查询参数和匹配器。
有关请求/响应匹配的更多信息,请参阅[匹配][getting_started/matching]。
在你的持续集成中使用
Xcode的预操作和后操作不会尊重非零脚本退出状态,因此如果您在发布到Pact代理失败时,您的构建也不会失败。如果您希望在CI过程中将Pact文件上传到Pact代理,我们建议您在CI工作流中创建一个专门的步骤来执行此操作。
请见pact-ruby-standalone页面获取安装说明和如何使用pact-broker
客户端的信息。
验证您的客户端与您正在集成的服务
如果您的设置正确,并且您的测试是对打包模拟服务器运行的,那么您应该在这里看到日志文件:$YOUR_PROJECT/tmp/pact.log
以及生成的pacts在这里:$YOUR_PROJECT/tmp/pacts/...
。
将生成的pacts文件发布到您的Pact代理或托管Pact代理,这样您的API提供者就可以始终从单一位置检索它们,即使pacts发生更改。或者,甚至只需通过简单地将pacts文件发送给您的API提供者开发者,让他们可以在他们的API响应测试中使用它们。有关更多信息,请参阅验证pacts。有关具有Ruby后端服务的端到端示例,请参阅KatKit示例。
还可以查看关于使用带有提供者状态的这个基于Docker的Node.js服务的文章。