如何对异步API进行单元测试?

我已将Google Toolbox for Mac安装到Xcode中,并按照说明设置了在此处找到的单元测试。

这一切都很好,我可以在我的所有对象上测试我的同步方法。 然而,我实际上想通过在委托上调用一个方法来异步测试大多数复杂的API,例如对文件下载和更新系统的调用将立即返回,然后在文件完成下载时运行-fileDownloadDidComplete:方法。

我如何测试这个单元测试?

看起来好像我想要testDownload函数,或者至少是测试框架'等待'fileDownloadDidComplete:方法来运行。

编辑:我现在已经切换到使用XCode内置XCTest系统,并发现Github上的TVRSMonitor提供了一个简单的方法来使用信号等待异步操作完成。

例如:

- (void)testLogin {
  TRVSMonitor *monitor = [TRVSMonitor monitor];
  __block NSString *theToken;

  [[Server instance] loginWithUsername:@"foo" password:@"bar"
                               success:^(NSString *token) {
                                   theToken = token;
                                   [monitor signal];
                               }

                               failure:^(NSError *error) {
                                   [monitor signal];
                               }];

  [monitor wait];

  XCTAssert(theToken, @"Getting token");
}

我遇到了同样的问题,并找到了适合我的不同解决方案。

我使用“老派”方法通过使用信号量将异步操作转换为同步流程,如下所示:

// create the object that will perform an async operation
MyConnection *conn = [MyConnection new];
STAssertNotNil (conn, @"MyConnection init failed");

// create the semaphore and lock it once before we start
// the async operation
NSConditionLock *tl = [NSConditionLock new];
self.theLock = tl;
[tl release];    

// start the async operation
self.testState = 0;
[conn doItAsyncWithDelegate:self];

// now lock the semaphore - which will block this thread until
// [self.theLock unlockWithCondition:1] gets invoked
[self.theLock lockWhenCondition:1];

// make sure the async callback did in fact happen by
// checking whether it modified a variable
STAssertTrue (self.testState != 0, @"delegate did not get called");

// we're done
[self.theLock release]; self.theLock = nil;
[conn release];

确保调用

[self.theLock unlockWithCondition:1];

然后在代表中。


我很欣赏这个问题在一年前被问及回答,但我不禁不同意给出的答案。 测试异步操作,尤其是网络操作是非常普遍的要求,对于正确使用它非常重要。 在给定的例子中,如果你依赖实际的网络响应,你将失去测试的一些重要价值。 具体而言,您的测试取决于您与之通信的服务器的可用性和功能正确性; 这种依赖性使你的测试

  • 更脆弱(如果服务器出现故障会发生什么?)
  • 不够全面(如何持续测试失败响应或网络错误?)
  • 想象一下测试这一点显着较慢:
  • 单元测试应该在几分之一秒内运行。 如果您每次运行测试都必须等待多秒的网络响应,那么您不太可能频繁地运行它们。

    单元测试主要是关于封装依赖; 从被测代码的角度来看,有两件事情会发生:

  • 您的方法可能通过实例化NSURLConnection来启动网络请求。
  • 您指定的委托通过某些方法调用接收响应。
  • 您的代理人不会或不应该关心响应的来源,无论是来自远程服务器的实际响应还是来自您的测试代码。 您可以利用这一点通过简单地自行生成响应来测试异步操作。 您的测试运行速度会更快,您可以可靠地测试成功或失败响应。

    这并不是说您不应该针对您正在使用的真实Web服务运行测试,但这些测试是集成测试并属于他们自己的测试套件。 该套件中的失败可能意味着Web服务发生了变化,或者简单地停机。 由于它们更脆弱,所以自动化它们比自动执行单元测试的价值更低。

    关于如何准确地测试对网络请求的异步响应,您有几个选项。 你可以简单地通过直接调用方法来测试委托(例如[someDelegate连接:连接didReceiveResponse:someResponse])。 这会有所作为,但有点不对。 您的对象提供的委托可能只是特定NSURLConnection对象的委托链中的多个对象中的一个; 如果直接调用委托方法,则可能会漏掉另一个委托人提供的一些关键功能。 作为一个更好的选择,你可以创建你创建的NSURLConnection对象,并让它将响应消息发送到它的整个代理链。 有些库会重新打开NSURLConnection(以及其他类)并为您执行此操作。 例如:https://github.com/pivotal/PivotalCoreKit/blob/master/SpecHelperLib/Extensions/NSURLConnection%2BSpec.m


    St3fan,你是一个天才。 非常感谢!

    这就是我使用你的建议做到的。

    'Downloader'用一个方法DownloadDidComplete定义一个协议,完成时触发。 有一个BOOL成员变量'downloadComplete'用于终止运行循环。

    -(void) testDownloader {
     downloadComplete = NO;
     Downloader* downloader = [[Downloader alloc] init] delegate:self];
    
     // ... irrelevant downloader setup code removed ...
    
     NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    
     // Begin a run loop terminated when the downloadComplete it set to true
     while (!downloadComplete && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    
    }
    
    
    -(void) DownloaderDidComplete:(Downloader*) downloader withErrors:(int) errors {
        downloadComplete = YES;
    
        STAssertNotEquals(errors, 0, @"There were errors downloading!");
    }
    

    当然,运行循环可能会永远运行..稍后我会改进它!

    链接地址: http://www.djcxy.com/p/50793.html

    上一篇: How to unit test asynchronous APIs?

    下一篇: Is there a way to control how pytest