如何测试异步方法? [英] How do you test an asynchronous method?

查看:116
本文介绍了如何测试异步方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个对象通过网络获取XML或JSON。一旦这个抓取完成,它调用一个选择器,传入返回的数据。所以,例如我会有像:

   - (void)testResponseWas200 
{
[ MyObject get:@foo.xmlwithTarget:self selector:@selector(dataFinishedLoading :)];
}

我尝试在Test类中实现dataFinishedLoading的路由,那个方法,但是测试套件只是锁定。这似乎是一个嘲笑的情况,但我想知道,如果其他人遇到这个和如何处理它。



FYI:我使用gh单位

解决方案

想到的三种方法是:NSRunLoop,semaphores和



NSRunLoop



  __ block bool finished = false; 

//为了测试的目的,我们创建这个异步任务
//在3秒后开始,需要1秒执行。
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); //将此替换为你的任务
finished = true;
});

//循环,直到从任务内部设置标志
while(!finished){
//每个循环上花费1秒处理事件
NSDate * oneSecond = [NSDate dateWithTimeIntervalSinceNow:1];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:oneSecond];
}

NSRunLoop是一个循环,处理网络端口,键盘或任何您插入的其他输入源,并在处理这些事件后或在时间限制后返回。当没有要处理的事件时,运行循环使线程进入睡眠状态。所有Cocoa和Core Foundation应用程序都有一个运行循环。您可以在Apple的线程编程指南中了解有关运行循环的更多信息:运行循环 或在Mike Ash Friday Q& A 2010-01-01:NSRunLoop Internals



在这个测试中,我只是使用NSRunLoop来休眠线程一秒钟。没有它, while 中的常量循环将占用CPU内核的100%。



布尔标志在同一个词法范围内创建(例如:在方法内部),那么该标志需要 __ block 存储限定符。如果标志是一个全局变量,它不会需要它。



如果测试在设置标志前崩溃,线程将永远等待。添加时间限制以避免:

  NSDate * timeout = [NSDate dateWithTimeIntervalSinceNow:2] 
while(!complete&& [timeout timeIntervalSinceNow]> 0){
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]
}
if(!finished)NSLog(@test failed with timeout);

如果您使用此代码进行单元测试,则插入超时的另一种方法是调度用assert阻止:

  //取自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);
});



Semaphore



改变或者直到时间限制:

  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 

//使用全局队列在3秒后通知信号量
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);
}

//等待时间限制为5秒
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 did not signal in time);
}

dispatch_release(semaphore);如果相反,我们用 dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)永久等待;












b $ b

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

  dispatch_group_t group = dispatch_group_create 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0UL);

//将工作分派到给定的组和队列
dispatch_group_async(group,queue,^ {
sleep(1); //将此替换为任务
});

//等待两秒完成组
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(@失败,调度组没有及时完成);
}

dispatch_release(group);

如果由于某种原因(清理资源?),完成后,使用 dispatch_group_notify(group,queue,^ {/*...*/});


I have an object that fetches XML or JSON over a network. Once this fetching is complete it calls a selector, passing in the returned data. So, for example I'd have something like:

-(void)testResponseWas200
{
    [MyObject get:@"foo.xml" withTarget:self selector:@selector(dataFinishedLoading:)];  
}

I tried the route of implementing dataFinishedLoading in the Test class and attempting to test inside that method, but the test suite is just locking up. This seems like it's a case for mocking, but I'm wondering if others have encountered this and how they handled it.

FYI: I'm using gh-unit for testing and any method prefixed with test* is executed automatically.

解决方案

Three ways that come to mind are: NSRunLoop, semaphores, and groups.

NSRunLoop

__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];
}

A NSRunLoop is a loop that processes events like network ports, keyboard, or any other input source you plug in, and returns after processing those events, or after a time limit. When there are no events to process, the run loop puts the thread to sleep. All Cocoa and Core Foundation applications have a run loop underneath. You can read more about run loops in Apple's Threading Programming Guide: Run Loops, or in Mike Ash Friday Q&A 2010-01-01: NSRunLoop Internals.

In this test, I'm just using the NSRunLoop to sleep the thread for a second. Without it, the constant looping in the while would consume 100% of a CPU core.

If the block and the boolean flag are created in the same lexical scope (eg: both inside a method), then the flag needs the __block storage qualifier to be mutable. Had the flag been a global variable, it wouldn't need it.

If the test crashes before setting the flag, the thread is stuck waiting forever. Add a time limit to avoid that:

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");

If you are using this code for unit testing, an alternative way to insert a timeout is to dispatch a block with an assert:

// 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");
});

Semaphore

Similar idea but sleeping until a semaphore changes, or until a time limit:

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);

If instead we waited forever with dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); we would be stuck until getting a signal from the task, which keeps running on the background queue.

Group

Now imagine you have to wait for several blocks. You can use an int as flag, or create a semaphore that starts with a higher number, or you can group the blocks and wait until the group is finished. In this example I do the later with just one block:

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);

If for some reason (to clean up resources?) you want to run a block after the group is finished, use dispatch_group_notify(group,queue, ^{/*...*/});

这篇关于如何测试异步方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆