首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何测试异步方法?

如何测试异步方法?
EN

Stack Overflow用户
提问于 2009-05-04 03:12:34
回答 4查看 4.3K关注 0票数 17

我有一个通过网络获取XML或JSON的对象。一旦获取完成,它就会调用选择器,传入返回的数据。所以,举个例子,我的代码是这样的:

代码语言:javascript
复制
-(void)testResponseWas200
{
    [MyObject get:@"foo.xml" withTarget:self selector:@selector(dataFinishedLoading:)];  
}

我尝试了在测试类中实现dataFinishedLoading的路线,并尝试在该方法中进行测试,但测试套件就是锁定了。这看起来像是一个嘲笑的案例,但我想知道其他人是否遇到过这种情况,以及他们是如何处理的。

仅供参考:我使用gh-unit进行测试,任何以test*为前缀的方法都会自动执行。

EN

回答 4

Stack Overflow用户

发布于 2012-06-25 01:13:46

出现在脑海中的三种方式是: NSRunLoop、信号量和组。

NSRunLoop

代码语言:javascript
复制
__block bool finished = false;

// For testing purposes we create this asynchronous task 
// that starts after 3 seconds and takes 1 second to execute.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_time_t threeSeconds = dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC);
dispatch_after(threeSeconds, queue, ^{ 
    sleep(1); // replace this with your task
    finished = true; 
});

// loop until the flag is set from inside the task
while (!finished) {
    // spend 1 second processing events on each loop
    NSDate *oneSecond = [NSDate dateWithTimeIntervalSinceNow:1];
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:oneSecond];
}

NSRunLoop是一个循环,它处理网络端口、键盘或您插入的任何其他输入源等事件,并在处理这些事件后或在一段时间限制后返回。当没有要处理的事件时,run循环使线程进入休眠状态。所有Cocoa和Core Foundation应用程序都有一个运行循环。你可以在苹果的线程编程指南:或Mike Ash 中阅读更多关于run循环的内容。

在这个测试中,我只是使用NSRunLoop让线程休眠一秒钟。如果没有它,while中的常量循环将消耗100%的CPU核心。

如果块和布尔标志是在相同的词法作用域中创建的(例如:都在一个方法中),那么该标志需要__block storage qualifier是可变的。如果标志是一个全局变量,它就不需要它了。

如果在设置标志之前测试崩溃,线程将永远等待。添加一个时间限制来避免这种情况:

代码语言:javascript
复制
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:2];
while (!finished && [timeout timeIntervalSinceNow]>0) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
                             beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
if (!finished) NSLog(@"test failed with timeout");

如果您正在使用此代码进行单元测试,则插入超时的另一种方法是使用断言分派一个块:

代码语言:javascript
复制
// taken from https://github.com/JaviSoto/JSBarrierOperationQueue/blob/master/JSBarrierOperationQueueTests/JSBarrierOperationQueueTests.m#L118
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2LL * NSEC_PER_SEC);
dispatch_after(timeout, dispatch_get_main_queue(), ^(void){
    STAssertTrue(done, @"Should have finished by now");
});

信号量

类似的想法,但休眠直到信号量改变,或直到时间限制:

代码语言:javascript
复制
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

// signal the semaphore after 3 seconds using a global queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3LL*NSEC_PER_SEC), queue, ^{ 
    sleep(1);
    dispatch_semaphore_signal(semaphore);
});

// wait with a time limit of 5 seconds
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5LL*NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout)==0) {
    NSLog(@"success, semaphore signaled in time");
} else {
    NSLog(@"failure, semaphore didn't signal in time");
}

dispatch_release(semaphore);

相反,如果我们一直在等待dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);,我们就会被卡住,直到任务发出信号,它会一直在后台队列中运行。

现在想象一下,你必须等待几个街区。您可以使用int作为标志,或者创建一个以更高数字开头的信号量,或者您可以对块进行分组,并等待分组完成。在这个例子中,我只用了一个块就完成了后面的操作:

代码语言:javascript
复制
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);

// dispatch work to the given group and queue
dispatch_group_async(group,queue,^{
    sleep(1); // replace this with your task
});

// wait two seconds for the group to finish
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2LL*NSEC_PER_SEC);
if (dispatch_group_wait(group, timeout)==0) {
    NSLog(@"success, dispatch group completed in time");
} else {
    NSLog(@"failure, dispatch group did not complete in time");
}

dispatch_release(group);

如果出于某种原因(为了清理资源?)要在组完成后运行一个块,请使用dispatch_group_notify(group,queue, ^{/*...*/});

票数 30
EN

Stack Overflow用户

发布于 2009-05-18 07:29:04

票数 1
EN

Stack Overflow用户

发布于 2013-12-17 23:58:32

@jano谢谢你我从你的帖子里做了这个小工具

在PYTestsUtils.m中

代码语言:javascript
复制
+ (void)waitForBOOL:(BOOL*)finished forSeconds:(int)seconds {
    NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:seconds];
    while (!*finished && [timeout timeIntervalSinceNow]>0) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }
}

在我的测试文件

代码语言:javascript
复制
- (void)testSynchronizeTime
{
    __block BOOL finished = NO;
    [self.connection synchronizeTimeWithSuccessHandler:^(NSTimeInterval serverTime) {
        NSLog(@"ServerTime %f", serverTime);
        finished = YES;
    } errorHandler:^(NSError *error) {
        STFail(@"Cannot get ServerTime %@", error);
        finished = YES;
    }];

    [PYTestsUtils waitForBOOL:&finished forSeconds:10];
    if (! finished)
        STFail(@"Cannot get ServerTime within 10 seconds");

}

变化

添加PYTestsUtils.m

代码语言:javascript
复制
+ (void)execute:(PYTestExecutionBlock)block ifNotTrue:(BOOL*)finished afterSeconds:(int)seconds {
    [self waitForBOOL:finished forSeconds:seconds];
    if (! *finished) block();
}

用法:

代码语言:javascript
复制
- (void)testSynchronizeTime
{
    __block BOOL finished = NO;
    [self.connection synchronizeTimeWithSuccessHandler:^(NSTimeInterval serverTime) {
        NSLog(@"ServerTime %f", serverTime);
        finished = YES;
    } errorHandler:^(NSError *error) {
        STFail(@"Cannot get ServerTime %@", error);
        finished = YES;
    }];

    [PYTestsUtils execute:^{
        STFail(@"Cannot get ServerTime within 10 seconds");
    } ifNotTrue:&finished afterSeconds:10];

}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/818674

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档