简体   繁体   English

如何对NSURLConnection代表进行单元测试?

[英]how to unit test a NSURLConnection Delegate?

How can I unit test my NSURLConnection delegate? 我如何对我的NSURLConnection代表进行单元测试? I made a ConnectionDelegate class which conforms to different protocols to serve data from the web to different ViewControllers. 我创建了一个ConnectionDelegate类,它符合不同的协议,以便将数据从Web提供给不同的ViewControllers。 Before I get too far I want to start writing my unit tests. 在我走得太远之前,我想开始编写单元测试。 But I don't know how to test them as a unit without the internet connection. 但我不知道如何在没有互联网连接的情况下将它们作为一个单元进行测试。 I would like also what I should do to treat the asynchronous callbacks. 我还想对待异步回调我应该怎么做。

This is similar to Jon's response, couldn't fit it into a comment, though. 这与Jon的回应类似,但不能将其纳入评论。 The first step is to make sure you are not creating a real connection. 第一步是确保您没有创建真正的连接。 The easiest way to achieve this is to pull the creation of the connection into a factory method and then substitute the factory method in your test. 实现此目的的最简单方法是将连接的创建拉入工厂方法,然后在测试中替换工厂方法。 With OCMock's partial mock support this could look like this. 有了OCMock的部分模拟支持,这可能看起来像这样。

In your real class: 在你真正的课堂上:

- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request
{
    return [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

In your test: 在你的测试中:

id objectUnderTest = /* create your object */
id partialMock = [OCMockObject partialMockForObject:objectUnderTest];
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc] 
    initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"file:foo"]] 
    delegate:nil startImmediately:NO];
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]];

Now, when your object under test tries to create the URL connection it actually gets the dummy connection created in the test. 现在,当您的测试对象尝试创建URL连接时,它实际上会获得在测试中创建的虚拟连接。 The dummy connection doesn't have to be valid, because we're not starting it and it never gets used. 虚拟连接不必有效,因为我们没有启动它,它永远不会被使用。 If your code does use the connection you could return another mock, one that mocks NSURLConnection. 如果您的代码确实使用了连接,那么您可以返回另一个模拟,一个模拟NSURLConnection。

The second step is to invoke the method on your object that triggers the creation of the NSURLConnection: 第二步是在对象上调用触发NSURLConnection创建的方法:

[objectUnderTest doRequest];

Because the object under test is not using the real connection we can now call the delegate methods from the test. 因为被测对象没有使用真正的连接,所以我们现在可以从测试中调用委托方法。 For the NSURLResponse we're using another mock, the response data is created from a string that's defined somewhere else in the test: 对于NSURLResponse,我们使用另一个模拟,响应数据是从测试中其他地方定义的字符串创建的:

int statusCode = 200;
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]];
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode];
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock];

NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding];
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData];

[objectUnderTest connectionDidFinishLoading:dummyUrlConnection];

That's it. 而已。 You've effectively faked all the interactions the object under test has with the connection, and now you can check whether it is in the state it should be in. 您已经有效地伪造了被测对象与连接的所有交互,现在您可以检查它是否处于应该处于的状态。

If you want to see some "real" code, have a look at the tests for a class from the CCMenu project that uses NSURLConnections. 如果您想查看一些“真实”代码,请查看使用NSURLConnections的CCMenu项目中的类的测试。 This is a little bit confusing because the class that's tested is named connection, too. 这有点令人困惑,因为测试的类也被称为连接。

http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup

EDIT (2-18-2014): I just stumbled across this article with a more elegant solution. 编辑(2014年2月18日):我只是偶然发现了一篇更优雅的解决方案。

http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/ http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/

Essentially, you have the following method: 基本上,您有以下方法:

- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
        if([timeoutDate timeIntervalSinceNow] < 0.0)
            break;
    } while (!done);

    return done;
}

At the end of your test method, you make sure things haven't timed out: 在测试方法结束时,您确保事情没有超时:

STAssertTrue([self waitForCompletion:5.0], @"Timeout");

Basic format: 基本格式:

- (void)testAsync
{
    // 1. Call method which executes something asynchronously 
    [obj doAsyncOnSuccess:^(id result) {
        STAssertNotNil(result);
        done = YES;
    }
    onError:^(NSError *error) [
        STFail();
        done = YES;
    }

    // 2. Determine timeout
    STAssertTrue([self waitForCompletion:5.0], @"Timeout");
}    

============== ==============

I'm late to the party, but I came across a very simple solution. 我迟到了,但我遇到了一个非常简单的解决方案。 (Many thanks to http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html ) (非常感谢http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html

.h file: .h文件:

@property (nonatomic) BOOL isDone;

.m file: .m文件:

- (void)testAsynchronousMethod
{
    // 1. call method which executes something asynchronously.

    // 2. let the run loop do its thing and wait until self.isDone == YES
    self.isDone = NO;
    NSDate *untilDate;
    while (!self.isDone)
    {
        untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0]
        [[NSRunLoop currentRunLoop] runUntilDate:untilDate];
        NSLog(@"Polling...");
    }

    // 3. test what you want to test
}

isDone is set to YES in the thread that the asynchronous method is executing. 在执行异步方法的线程中, isDone设置为YES

So in this case, I created and started the NSURLConnection at step 1 and made the delegate of it this test class. 所以在这种情况下,我在第1步创建并启动了NSURLConnection,并将其委托给了这个测试类。 In

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

I set self.isDone = YES; 我设置了self.isDone = YES; . We break out of the while loop and the test is executed. 我们突破while循环并执行测试。 Done. 完成。

I avoid networking in unit tests. 我在单元测试中避免联网。 Instead: 代替:

  • I isolate NSURLConnection within a method. 我在一个方法中隔离了NSURLConnection。
  • I create a testing subclass, overriding that method to remove all traces of NSURLConnection. 我创建了一个测试子类,重写该方法以删除NSURLConnection的所有痕迹。
  • I write one test to ensure that the method in question will get invoked when I want. 我写了一个测试,以确保在我想要的时候调用有问题的方法。 Then I know it'll fire off an NSURLConnection in real life. 然后我知道它会在现实生活中触发NSURLConnection。

Then I concentrate on the more interesting part: Synthesize mock NSURLResponses with various characteristics, and pass them to the NSURLConnectionDelegate methods. 然后我专注于更有趣的部分:合成具有各种特征的模拟NSURLResponses,并将它们传递给NSURLConnectionDelegate方法。

My favorite way of doing this is to subclass NSURLProtocol and have it respond to all http requests - or other protocols for that matter. 我最喜欢这样做的方法是子类NSURLProtocol并让它响应所有http请求 - 或其他协议。 You then register the test protocol in your -setup method and unregisters it in your -tearDown method. 然后,在-setup方法中注册测试协议,并在-tearDown方法中注销它。 You can then have this test protocol serve some well known data back to your code so you can validate it in your unit tests. 然后,您可以让此测试协议将一些众所周知的数据提供给您的代码,以便您可以在单元测试中对其进行验证。

I have written a few blog articles about this subject. 我写了几篇关于这个主题的博客文章。 The most relevant for your problem would probably be Using NSURLProtocol for Injecting Test Data and Unit Testing Asynchronous Network Access . 与您的问题最相关的可能是使用NSURLProtocol注入测试数据单元测试异步网络访问

You may also want to take a look my ILCannedURLProtocol which is described in the previous articles. 您可能还想查看我之前的文章中描述的ILCannedURLProtocol。 The source is available at Github . 该来源可在Github上获得

Here's what I do: 这是我做的:

  1. Get XAMPP Control Panel http://www.apachefriends.org/en/xampp.html 获取XAMPP控制面板http://www.apachefriends.org/en/xampp.html
  2. Start the apache server 启动apache服务器
  3. In your ~/Sites folder, put a test file (whatever data you want, we'll call it my file.test ). 在你的~/Sites文件夹中,输入一个测试文件(你想要的任何数据,我们称之为my file.test )。
  4. Start your delegate using the URL http://localhost/~username/myfile.test 使用URL http://localhost/~username/myfile.test启动您的委托
  5. Stop the apache server when not using it. 不使用时停止apache服务器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM