如何测试异步方法?

编程入门 行业动态 更新时间:2024-10-25 18:24:29
本文介绍了如何测试异步方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我有一个对象通过网络获取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阻止:

//取自github/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 github/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, ^{/*...*/});

更多推荐

如何测试异步方法?

本文发布于:2023-11-03 16:33:32,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1555594.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:测试   方法

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!