Jan*_*yne 23 objective-c ios xctest nsurlsession nsurlsessiontask
我想知道如何使用NSURLSession"单元测试"HTTP请求和响应.现在,当作为单元测试运行时,我的完成块代码不会被调用.但是,当从AppDelegate(didFinishWithLaunchingOptions)中执行相同的代码时,将调用完成块内的代码.正如在这个线程中所建议的那样,NSURLSessionDataTask dataTaskWithURL完成处理程序没有被调用,需要使用信号量和/或dispatch_group"以确保在网络请求完成之前阻塞主线程".
我的HTTP邮政编码如下所示.
@interface LoginPost : NSObject
- (void) post;
@end
@implementation LoginPost
- (void) post
{
NSURLSessionConfiguration* conf = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:conf delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSURL* url = [NSURL URLWithString:@"http://www.example.com/login"];
NSString* params = @"username=test@xyz.com&password=test";
NSMutableRequest* request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Response:%@ %@\n", response, error); //code here never gets called in unit tests, break point here never is triggered as well
//response is actually deserialized to custom object, code omitted
}];
[task resume];
}
@end
Run Code Online (Sandbox Code Playgroud)
测试它的方法如下所示.
- (void) testPost
{
LoginPost* loginPost = [LoginPost alloc];
[loginPost post];
//XCTest continues by operating assertions on deserialized HTTP response
//code omitted
}
Run Code Online (Sandbox Code Playgroud)
在我看来AppDelegate,完成块代码确实有效,它看起来如下所示.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//generated code
LoginPost* loginPost = [LoginPost alloc];
[loginPost post];
}
Run Code Online (Sandbox Code Playgroud)
在单元测试中运行时如何获得完成块的任何指针?我是iOS的新手,所以一个明确的例子真的会有所帮助.
Rob*_*Rob 51
Xcode 6现在可以处理异步测试XCTestExpectation.在测试异步进程时,建立异常完成此进程的"期望",在发出异步进程后,等待期望在一段固定时间内得到满足,当查询完成时,您将异步进行异步处理满足期望.
例如:
- (void)testDataTask
{
XCTestExpectation *expectation = [self expectationWithDescription:@"asynchronous request"];
NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error, @"dataTaskWithURL error %@", error);
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
}
XCTAssert(data, @"data nil");
// do additional tests on the contents of the `data` object here, if you want
// when all done, Fulfill the expectation
[expectation fulfill];
}];
[task resume];
[self waitForExpectationsWithTimeout:10.0 handler:nil];
}
Run Code Online (Sandbox Code Playgroud)
我之前的答案在下面提到,XCTestExpectation但我会将其保留用于历史目的.
由于您的测试正在主队列上运行,并且由于您的请求是异步运行的,因此您的测试将不会捕获完成块中的事件.您必须使用信号量或调度组来使请求同步.
例如:
- (void)testDataTask
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error, @"dataTaskWithURL error %@", error);
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
}
XCTAssert(data, @"data nil");
// do additional tests on the contents of the `data` object here, if you want
// when all done, signal the semaphore
dispatch_semaphore_signal(semaphore);
}];
[task resume];
long rc = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 60.0 * NSEC_PER_SEC));
XCTAssertEqual(rc, 0, @"network request timed out");
}
Run Code Online (Sandbox Code Playgroud)
信号量将确保在请求完成之前测试不会完成.
显然,我上面的测试只是发出一个随机的HTTP请求,但希望它能说明这个想法.我的各种XCTAssert陈述将确定四种类型的错误:
该NSError物体是不是零.
HTTP状态代码不是200.
该NSData目标是零.
完成块未在60秒内完成.
您可能还会为响应的内容添加测试(我在此简化示例中没有这样做).
注意,上面的测试是有效的,因为我的完成块中没有任何内容可以向主队列分派任何内容.如果你使用需要主队列的异步操作来测试它(如果你不小心使用AFNetworking或者你自己手动调度到主队列就会发生这种情况),你可能会遇到上述模式的死锁(因为我们阻塞了)等待网络请求完成的主线程).但NSURLSession就此而言,这种模式效果很好.
您询问了如何从命令行进行测试,而不依赖于模拟器.有几个方面:
如果要从命令行进行测试,可以xcodebuild从命令行使用.例如,要从命令行在模拟器上进行测试,它将是(在我的示例中,我的方案被调用NetworkTest):
xcodebuild test -scheme NetworkTest -destination 'platform=iOS Simulator,name=iPhone Retina (3.5-inch),OS=7.0'
这将构建该方案并在指定的目标上运行它.注意,有很多关于Xcode 5.1问题的报告从命令行在模拟器上测试应用程序xcodebuild(我可以验证这种行为,因为我有一台机器,上面的工作正常,但它冻结在另一台机器上).Xcode 5.1中针对模拟器的命令行测试似乎并不完全可靠.
如果您不希望您的测试在模拟器上运行(这适用于从命令行或从Xcode执行),那么您可以构建一个MacOS X目标,并为该构建创建一个关联的方案.例如,我在我的应用程序中添加了一个Mac OS X目标,然后添加了一个名为NetworkTestMacOS它的方案.
顺便说一句,如果您将Mac OS X方案添加到现有iOS项目中,测试可能不会自动添加到方案中,因此您可能必须通过编辑方案手动执行此操作,导航到测试部分并添加测试上课.然后,您可以通过选择正确的方案从Xcode运行那些Mac OS X测试,或者也可以从命令行执行:
xcodebuild test -scheme NetworkTestMacOS -destination 'platform=OS X,arch=x86_64'
另请注意,如果您已经构建了目标,则可以通过导航到正确的DerivedData文件夹(在我的示例中,它~/Library/Developer/Xcode/DerivedData/NetworkTest-xxx/Build/Products/Debug)直接运行这些测试 ,然后xctest直接从命令行运行:
/Applications/Xcode.app/Contents/Developer/usr/bin/xctest -XCTest All NetworkTestMacOSTests.xctest
将测试与Xcode会话隔离的另一个选择是在单独的OS X Server上进行测试.请参阅Xcode中 WWDC 2013视频测试的持续集成和测试部分.进入这里远远超出了原始问题的范围,所以我只是简单地将您介绍给该视频,该视频对该主题进行了很好的介绍.
就个人而言,我非常喜欢Xcode中的测试集成(它使测试的调试变得更加容易),并且通过拥有Mac OS X目标,您可以绕过该过程中的模拟器.但是,如果您想从命令行(或OS X Server)执行此操作,可能上述帮助.
| 归档时间: |
|
| 查看次数: |
12640 次 |
| 最近记录: |