objective-c - 完成时调用主队列的单元测试异步队列模式

标签 objective-c grand-central-dispatch sentestingkit

这和我之前的question有关, 但足够不同,我想我会把它扔进一个新的。我有一些代码在自定义队列上运行异步,然后在完成时在主线程上执行一个完成 block 。我想围绕这个方法编写单元测试。我在 MyObject 上的方法如下所示。

+ (void)doSomethingAsyncThenRunCompletionBlockOnMainQueue:(void (^)())completionBlock {

    dispatch_queue_t customQueue = dispatch_queue_create("com.myObject.myCustomQueue", 0);

    dispatch_async(customQueue, ^(void) {

        dispatch_queue_t currentQueue = dispatch_get_current_queue();
        dispatch_queue_t mainQueue = dispatch_get_main_queue();

        if (currentQueue == mainQueue) {
            NSLog(@"already on main thread");
            completionBlock();
        } else {
            dispatch_async(mainQueue, ^(void) {
                NSLog(@"NOT already on main thread");
                completionBlock();
        }); 
    }
});

为了额外的安全,我加入了主队列测试,但它总是命中 dispatch_async。我的单元测试如下所示。

- (void)testDoSomething {

    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    void (^completionBlock)(void) = ^(void){        
        NSLog(@"Completion Block!");
        dispatch_semaphore_signal(sema);
    }; 

    [MyObject doSomethingAsyncThenRunCompletionBlockOnMainQueue:completionBlock];

    // Wait for async code to finish
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_release(sema);

    STFail(@"I know this will fail, thanks");
}

我创建了一个信号量以阻止测试在异步代码完成之前完成。如果我不需要完成 block 在主线程上运行,这会很好用。然而,正如一些人在我上面链接的问题中指出的那样,测试在主线程上运行然后我将完成 block 排入主线程的事实意味着我将永远挂起。

从异步队列调用主队列是一种我经常看到的用于更新 UI 等的模式。有没有人有更好的模式来测试回调主队列的异步代码?

最佳答案

有两种方法可以让 block 分派(dispatch)到主队列运行。第一个是通过 dispatch_main,正如 Drewsmits 所提到的。然而,正如他还指出的那样,在您的测试中使用 dispatch_main 存在一个大问题:它永远不会返回。它只会坐在那里等待运行任何在永恒的剩余时间里出现的障碍。正如您想象的那样,这对单元测试没有太大帮助。

幸运的是,还有另一种选择。在 dispatch_main man pageCOMPATIBILITY 部分中,它是这样说的:

Cocoa applications need not call dispatch_main(). Blocks submitted to the main queue will be executed as part of the "common modes" of the application's main NSRunLoop or CFRunLoop.

换句话说,如果您在 Cocoa 应用程序中,调度队列会被主线程的 NSRunLoop 耗尽。所以我们需要做的就是在等待测试完成时保持运行循环运行。它看起来像这样:

- (void)testDoSomething {

    __block BOOL hasCalledBack = NO;

    void (^completionBlock)(void) = ^(void){        
        NSLog(@"Completion Block!");
        hasCalledBack = YES;
    }; 

    [MyObject doSomethingAsyncThenRunCompletionBlockOnMainQueue:completionBlock];

    // Repeatedly process events in the run loop until we see the callback run.

    // This code will wait for up to 10 seconds for something to come through
    // on the main queue before it times out. If your tests need longer than
    // that, bump up the time limit. Giving it a timeout like this means your
    // tests won't hang indefinitely. 

    // -[NSRunLoop runMode:beforeDate:] always processes exactly one event or
    // returns after timing out. 

    NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10];
    while (hasCalledBack == NO && [loopUntil timeIntervalSinceNow] > 0) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:loopUntil];
    }

    if (!hasCalledBack)
    {
        STFail(@"I know this will fail, thanks");
    }
}

关于objective-c - 完成时调用主队列的单元测试异步队列模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7817605/

相关文章:

iphone - NSDate : timeIntervalSinceNow crash

objective-c - 如何为框架配置 OCUnit 测试包?

swift - 我正在尝试使用调度背景在 Collection View 中使用本地标识符来获取 Assets ,但加载需要太多时间并且单元格为空

ios - dispatch_async超时方法调用

ios - CIFilter似乎在GPU上失败

ios - 用于 iOS 单元测试的动态 "test host"或捆绑加载器?

单元测试期间的 Xcode CI bot 错误(Unexpected TestSuiteWillFinish)

objective-c - NSImage colorWithPatternImage : how to tile images from the top instead?

ios - 如何设置 sentestkit-test 运行的顺序?

ios - 将生成的 GIF 保存到相机胶卷?