集成测试中记录和重放网络事件的简单的 XCTestCase 子类
这是一个用于自动化集成测试中记录和重放网络事件的简单测试框架。为了避免创建网络存根的繁琐,它将实时网络请求和响应记录下来,然后在后续运行中回放它们(多亏了绝佳的OHHTTPStubs),这样您的软件可以不间断地进行集成而不会出现故障。
要运行示例项目,请克隆仓库,然后首先从 Example 目录运行 pod install
BeKindRewind 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中。
pod "BeKindRewind"
#import <BeKindRewind/BeKindRewind.h>
@interface BeKindRewindExampleTestCase : BKRTestCase
@end
@implementation BeKindRewindExampleTestCase
- (BOOL)isRecording {
return YES;
}
- (void)testSimpleNetworkCall {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get?test=test"]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
// don't forget to create a test expectation, this has the __block annotation because to avoid a retain cycle
// XCTestExpectation is necessary for asynchronous network activity, BeKindRewind will take care of everything else
__block XCTestExpectation *networkExpectation = [self expectationWithDescription:@"network"];
NSURLSessionDataTask *basicGetTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error);
XCTAssertNotNil(response);
XCTAssertNotNil(data);
NSDictionary *dataDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
XCTAssertNil(error);
XCTAssertEqualObjects(dataDict[@"args"], @{@"test" : @"test"});
// fulfill the expectation
[networkExpectation fulfill];
}];
[basicGetTask resume];
// explicitly wait for the expectation
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
// Assert fail if timeout encounters an error
XCTAssertNil(error);
}];
}
@end
运行此测试后,将在控制台输出类似以下内容的日志文件
2016-03-02 10:09:27.630 xctest[92201:14865359] <BKRRecordableVCR: 0x7fb0a9714ce0>: trying to write cassette to: /Users/jordanz/Library/Developer/CoreSimulator/Devices/611CC72A-11D4-4DD2-8471-FF2F65413BC7/data/Documents/BeKindRewindExampleTestCase.bundle/testSimpleNetworkCall.plist
将此文件拖到您的项目中,并以您的测试用例名命名(它会自动命名)。
然后将 isRecording
值设置为 NO
- (BOOL)isRecording {
return NO;
}
然后在后续运行中,测试将使用记录的文件来响应匹配的网络请求。为了确保使用了记录,请在与您的记录相关的信息上断言。以下是修改以上测试以测试测试执行期间返回的 NSHTTPURLResponse 对象中的 Date header 的示例。这是在创建记录 después 更新测试的示例。
#import <BeKindRewind/BeKindRewind.h>
@interface BeKindRewindExampleTestCase : BKRTestCase
@end
@implementation BeKindRewindExampleTestCase
- (BOOL)isRecording {
return NO;
}
- (void)testSimpleNetworkCall {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get?test=test"]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
// don't forget to create a test expectation, this has the __block annotation to avoid a retain cycle
// XCTestExpectation is necessary for asynchronous network activity, BeKindRewind will take care of everything else
__block XCTestExpectation *networkExpectation = [self expectationWithDescription:@"network"];
NSURLSessionDataTask *basicGetTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error);
XCTAssertNotNil(response);
XCTAssertNotNil(data);
NSDictionary *dataDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
XCTAssertNil(error);
XCTAssertEqualObjects(dataDict[@"args"], @{@"test" : @"test"});
// Now this ensures that the recording returned is the stub and not an accidental "live" request. This is
// important for stable testing
XCTAssertEqualObjects([(NSHTTPURLResponse *)response allHeaderFields][@"Date"], @"Wed, 02 Mar 2016 18:09:28 GMT");
// fulfill the expectation
[networkExpectation fulfill];
}];
[basicGetTask resume];
// explicitly wait for the expectation
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
// Assert fail if timeout encounters an error
XCTAssertNil(error);
}];
}
@end
这些将自动设置。如果这些值足够,您可以随意覆盖它们,但如果没有必要,则不需要这样做。这些默认值可能在版本 1.0 之前发生变化。关于记录和播放的说明:它们只有在 [super setUp]
和 [super tearDown]
之间才有效。
- (BOOL)isRecording {
return YES;
}
- (NSString *)baseFixturesDirectoryFilePath {
return [BKRTestCaseFilePathHelper documentsDirectory];
}
- (BKRTestConfiguration *)testConfiguration {
return [BKRTestConfiguration defaultConfigurationWithTestCase:self];
}
- (id<BKRTestVCRActions>)testVCRWithConfiguration:(BKRTestConfiguration *)configuration {
return [BKRTestVCR vcrWithTestConfiguration:configuration];
}
- (NSString *)recordingCassetteFilePathWithBaseDirectoryFilePath:(NSString *)baseDirectoryFilePath {
NSParameterAssert(baseDirectoryFilePath);
return [BKRTestCaseFilePathHelper writingFinalPathForTestCase:self inTestSuiteBundleInDirectory:baseDirectoryFilePath];
}
- (BKRCassette *)playingCassette {
NSDictionary *cassetteDictionary = [BKRTestCaseFilePathHelper dictionaryForTestCase:self];
XCTAssertNotNil(cassetteDictionary);
return [BKRCassette cassetteFromDictionary:cassetteDictionary];
}
- (BKRCassette *)recordingCassette {
return [BKRCassette cassette];
}
在测试过程中,您可能会发现需要在请求未匹配时获取信息。在这种情况下,您可以在BKRTestConfiguration
对象中添加一个区块,当请求在播放时无法匹配时会执行此区块。这对于调试很有用,甚至可以用来在匹配严格的情况下使测试失败。
以下是一个配置您的BKRTestCase
子类以记录失败请求的示例
- (BKRTestConfiguration *)testConfiguration {
BKRTestConfiguration *configuration = [super testConfiguration];
configuration.requestMatchingFailedBlock = ^void (NSURLRequest *request) {
NSLog(@"Failed to match request: %@", request);
};
return configuration;
}
以下是一个使XCTestCase
更加严格的示例,如果匹配失败将测试用例标记为失败
- (BKRTestConfiguration *)testConfiguration {
BKRTestConfiguration *configuration = [super testConfiguration];
configuration.requestMatchingFailedBlock = ^void (NSURLRequest *request) {
XCTFail(@"Failed to match request: %@", request);
};
return configuration;
}
BeKindRewind仅在接收到resume
消息时记录网络事件。当创建NSURLSessionTask
对象时,它们处于NSURLSessionTaskStateSuspended
状态,并且只有在发送resume
消息后才会开始记录。一旦发送了resume
,它将记录直到测试结束(由您的XCTestExpectation
保护)。
建议您使用BKRTestVCR子类进行记录。它自动处理异步执行和XCTestCase等方面的问题。
默认情况下,BeKindRewind在播放时(模拟)网络活动时会期待每个测试用例都存在一个属性列表的固定文件。如果不存在,则抛出异常。这可以在BKRTestConfiguration对象中重写(或者其父类BKRConfiguration)。
如果您在测试期间看到类似于*** 应用因未捕获的异常 'NSInternalInconsistencyException' 而终止,原因是:API 故障 - 在等待模式下创建期望。
的异常,那么您可能需要调整您的BKRTestConfiguration
对象。默认的BKRTestConfiguration
构造函数(defaultConfigurationWithTestCase:
)在开始和结束网络操作时调用一个块以在操作周围创建一个XCTestExpectation
,以确保录音不被截断。如果您有在测试期间被触发的多个网络操作,其中一些发生在调用waitForExpectationsWithTimeout: handler:
之后,那么在测试用例运行期间应该将这些块设置为nil。你应该在BKRTestCase
子类中重写这一点(或者如果你自己实现了BKRVCR
,则在适当的地方重写。以下是在BKRTestCase
中重写的示例)
- (BKRTestConfiguration *)testConfiguration {
BKRTestConfiguration *defaultConfiguration = [super testConfiguration];
defaultConfiguration.beginRecordingBlock = nil;
defaultConfiguration.endRecordingBlock = nil;
return defaultConfiguration;
}
如果您看到由于名为reset
的XCTestExpectation的异步等待超时而导致测试失败,那么这很可能是由于测试用例在移除存根的同时尝试存根请求而导致的。失败将类似于以下内容
[21:32:10]: ▸ ✗ testCopyConfigurationWithSubscribedChannelsAndCallbackQueue, ((error) == nil) failed: "Error Domain=com.apple.XCTestErrorDomain Code=0 "The operation couldn’t be completed. (com.apple.XCTestErrorDomain error 0.)""
[21:32:10]: ▸ ✗ testCopyConfigurationWithSubscribedChannelsAndCallbackQueue, Asynchronous wait failed: Exceeded timeout of 60 seconds, with unfulfilled expectations: "reset".
为了修复此问题,请确保适当地拆除并停止任何可能的长运行或后台异步进程,特别是涉及网络的操作,以避免将这些竞争条件引入到测试中。
尽量编写不依赖于状态的测试。相反,确保当isRecording == YES
时,测试可以完全记录以进行回放,包括setUp和tearDown。这有助于开发,并确保测试不会被写入不能在其他开发者在尝试使用新的记录更新您的测试时重创的条件。
如果您要测试iOS 7或更低版本,请使用0.6.x或更低版本的JSZVCR。
乔丹·祖克,[电子邮件保护]:bcb0b9aeacacac6c096898c9e9aeaceeaclatloaticb9bcac
BeKindRewind 是在 MIT 许可证下可用的。更多信息请参阅 LICENSE 文件。