Ben*_*ton 64 iphone unit-testing asynchronous google-toolbox-for-mac
我已将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");
}
Run Code Online (Sandbox Code Playgroud)
Tho*_*ann 52
我遇到了同样的问题,发现了一个适合我的不同解决方案.
我使用"旧学校"方法通过使用信号量将异步操作转换为同步流,如下所示:
// 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];
Run Code Online (Sandbox Code Playgroud)
一定要调用
[self.theLock unlockWithCondition:1];
Run Code Online (Sandbox Code Playgroud)
然后在代表中.
Ada*_*gan 44
我很欣赏这个问题在一年前被提出并得到了回答,但我不禁对这些问题表示不同意见.测试异步操作,特别是网络操作,是一个非常常见的要求,并且对于正确的操作非常重要.在给定的示例中,如果您依赖于实际的网络响应,则会丢失测试的一些重要值.具体来说,您的测试取决于您正在与之通信的服务器的可用性和功能正确性; 这种依赖会使你的测试
单元测试应该在几分之一秒内完成.如果每次运行测试时都必须等待多秒的网络响应,那么您不太可能经常运行它们.
单元测试主要是关于封装依赖关系; 从您测试的代码的角度来看,有两件事情发生:
您的代表不会或不应该关注响应的来源,无论是来自远程服务器的实际响应还是来自您的测试代码.您可以通过自己简单地生成响应来利用此功能来测试异步操作.您的测试运行得更快,您可以可靠地测试成功或失败响应.
这并不是说您不应该针对您正在使用的真实Web服务运行测试,而是那些是集成测试并且属于他们自己的测试套件.该套件中的失败可能意味着Web服务发生了变化,或者只是简单地失败了.由于它们更脆弱,因此自动化它们的价值往往低于自动化单元测试.
关于如何测试对网络请求的异步响应,您有几个选择.您可以通过直接调用方法来单独测试委托(例如[someDelegate connection:connection didReceiveResponse:someResponse]).这会有所作为,但稍有不妥.您的对象提供的委托可能只是特定NSURLConnection对象的委托链中的多个对象之一; 如果你直接调用你的代理人的方法,你可能会错过由链上的另一个代表提供的一些关键功能.作为更好的替代方法,您可以存根您创建的NSURLConnection对象,并让它将响应消息发送到其整个委托链.有些库将重新打开NSURLConnection(以及其他类)并为您执行此操作.https://github.com/pivotal/PivotalCoreKit/blob/master/SpecHelperLib/Extensions/NSURLConnection%2BSpec.m
Ben*_*ton 19
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!");
}
Run Code Online (Sandbox Code Playgroud)
当然,运行循环可能会永远运行.我稍后会改进它!
Hol*_*ick 16
我写了一个小助手,可以很容易地测试异步API.首先是帮手:
static inline void hxRunInMainLoop(void(^block)(BOOL *done)) {
__block BOOL done = NO;
block(&done);
while (!done) {
[[NSRunLoop mainRunLoop] runUntilDate:
[NSDate dateWithTimeIntervalSinceNow:.1]];
}
}
Run Code Online (Sandbox Code Playgroud)
你可以像这样使用它:
hxRunInMainLoop(^(BOOL *done) {
[MyAsyncThingWithBlock block:^() {
/* Your test conditions */
*done = YES;
}];
});
Run Code Online (Sandbox Code Playgroud)
只有done变为才会继续TRUE,所以请务必在完成后设置.当然,如果你愿意,可以给助手添加超时,
这很棘手.我认为你需要在测试中设置一个runloop,并且能够为你的异步代码指定runloop.否则回调将不会发生,因为它们是在runloop上执行的.
我猜你可以在一个循环中短时间运行runloop.让回调设置一些共享状态变量.或者甚至可以简单地要求回调终止runloop.那样你就知道测试结束了.您应该能够通过在一段时间后停止循环来检查超时.如果发生这种情况,则会发生超时.
我从来没有这样做,但我想不久就会想到.请分享你的结果:-)
如果您正在使用AFNetworking或ASIHTTPRequest等库并通过NSOperation(或具有这些库的子类)管理您的请求,那么可以使用NSOperationQueue对测试/ dev服务器进行测试:
在测试中:
// create request operation
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[queue addOperation:request];
[queue waitUntilAllOperationsAreFinished];
// verify response
Run Code Online (Sandbox Code Playgroud)
这基本上运行runloop直到操作完成,允许所有回调在正常情况下在后台线程上发生.
要详细说明@ St3fan的解决方案,您可以在发起请求后尝试:
- (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;
}
Run Code Online (Sandbox Code Playgroud)
其他方式:
//block the thread in 0.1 second increment, until one of callbacks is received.
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
//setup timeout
float waitIncrement = 0.1f;
int timeoutCounter = (int)(30 / waitIncrement); //30 sec timeout
BOOL controlConditionReached = NO;
// Begin a run loop terminated when the downloadComplete it set to true
while (controlConditionReached == NO)
{
[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:waitIncrement]];
//control condition is set in one of your async operation delegate methods or blocks
controlConditionReached = self.downloadComplete || self.downloadFailed ;
//if there's no response - timeout after some time
if(--timeoutCounter <= 0)
{
break;
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
26009 次 |
| 最近记录: |