单元测试异步队列的模式,在完成时调用主队列

Dre*_*its 32 objective-c grand-central-dispatch sentestingkit

这与我之前提出的问题有关,但不同之处,我认为我会把它扔进一个新问题.我有一些代码在自定义队列上运行异步,然后在完成时在主线程上执行完成块.我想围绕这种方法编写单元测试.我的方法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();
        }); 
    }
});
Run Code Online (Sandbox Code Playgroud)

}

我投入了主队列测试以获得额外的安全性,但它总是能够击中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");
}
Run Code Online (Sandbox Code Playgroud)

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

从异步队列调用主队列是我看到很多用于更新UI等的模式.有没有人有更好的模式来测试回调到主队列的异步代码?

BJ *_*mer 58

有两种方法可以将调度的块分配给主队列来运行.第一个是通过dispatch_main,如Drewsmits所述.然而,正如他所指出的那样,dispatch_main在你的测试中使用有一个很大的问题:它永远不会返回.它只会坐在那里等待运行永恒的剩余部分.正如您可以想象的那样,单元测试并没有那么有用.

幸运的是,还有另一种选择.在手册页COMPATIBILITY部分中,它说:dispatch_main

Cocoa应用程序不需要调用dispatch_main().提交到主队列的块将作为应用程序主NSRunLoop或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");
    }
}
Run Code Online (Sandbox Code Playgroud)


qwz*_*bug 18

另一种方法,使用信号量和runloop搅拌.请注意,如果dispatch_semaphore_wait超时,则返回非零值.

- (void)testFetchSources
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [MyObject doSomethingAsynchronousWhenDone:^(BOOL success) {
        STAssertTrue(success, @"Failed to do the thing!");
        dispatch_semaphore_signal(semaphore);
    }];

    while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];

    dispatch_release(semaphore);
}
Run Code Online (Sandbox Code Playgroud)


dst*_*rkr 7

Square在他们的SocketRocket项目中包含了对SenTestCase的一个聪明的补充,这使得这很容易.你可以这样称呼它:

[self runCurrentRunLoopUntilTestPasses:^BOOL{
    return [someOperation isDone];
} timeout: 60 * 60];
Run Code Online (Sandbox Code Playgroud)

代码可在此处获得:

SenTestCase + SRTAdditions.h

SenTestCase + SRTAdditions.m


hfo*_*sli 7

到目前为止,BJ Homer的解决方案是最佳解决方案.我已经创建了一些基于该解决方案的宏.

在这里查看项目https://github.com/hfossli/AGAsyncTestHelper

- (void)testDoSomething {

    __block BOOL somethingIsDone = NO;

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

    [MyObject doSomethingAsyncThenRunCompletionBlockOnMainQueue:completionBlock];

    WAIT_WHILE(!somethingIsDone, 1.0); 
    NSLog(@"This won't be reached until async job is done");
}
Run Code Online (Sandbox Code Playgroud)

WAIT_WHILE(expressionIsTrue, seconds)直到表达式是不正确的或达到时间限制-macro将评估输入.我认为很难让它比这更清洁