0

我正在努力找出在后台线程中测试与 Core Data 交互的最佳方法。我有以下类方法:

+ (void)fetchSomeJSON
{
    // Download some json then parse it in the block
    [[AFHTTPClient sharedClient] fetchAllThingsWithCompletion:^(id results, NSError *error) {
        if ([results count] > 0) {

            NSManagedObjectContext *backgroundContext = //... create a new context for background insertion
            dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

            dispatch_async(background, ^{ // If I comment this out, my test runs just fine

                //... insert and update some entities
                for (NSString *str in results) {
                    NSManagedObject *object = //...
                }
            });
        }
    }];
}

我目前正在使用以下 Kiwi 代码测试此方法:

describe(@"MyAction", ^{

    __block void (^completionBlock)(NSArray *array, NSError *error);

    beforeEach(^{
        // Stub the http client
        id mockClient = [AFHTTPClient mock];
        [WRNAPIClient stub:@selector(sharedClient) andReturn:mockClient];

        // capture the block argument
        KWCaptureSpy *spy = [mockClient captureArgument:@selector(fetchAllThingsWithCompletion:) atIndex:0];
        [MyClass fetchSomeJSON]; // Call the method so we can capture the block
        completionBlock = spy.argument;

        // run the completion block 
        completionBlock(@[@"blah"], nil);
    })

    // If I remove the dispatch_async block, this test passes fine. 
    // If I add it in again the test fails, probably because its not waiting
    it(@"should return the right count", ^{
        // entityCount is a block that performs a fetch request count
        NSInteger count = entityCount(moc, @"Task");
        [[theValue(count) should] equal:theValue(4)];
    })

    // This works fine, but obviously I don't want to wait a second
    it(@"should return the right count after waiting for a second", ^{
        sleep(1);
        NSInteger count = entityCount(moc, @"Task");
        [[theValue(count) should] equal:theValue(4)];
    });

};

如果我删除该dispatch_async行,那么我可以让我的测试快速运行。我可以让我的测试套件在使用时运行的唯一方法dispatch_asyncsleep(1)在调用完成块之后。使用sleep()让我认为我没有以正确的方式接近它。我试过使用shouldEventually,但这似乎并没有重新获取我的count价值。

4

2 回答 2

2

你试过这些异步块宏吗?

#define TestNeedsToWaitForBlock() __block BOOL blockFinished = NO
#define BlockFinished() blockFinished = YES
#define WaitForBlock() while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !blockFinished)
于 2014-03-28T06:00:12.013 回答
1

我尝试了几种方法来解决这个问题,但没有一种感觉是对的。


1)移动dispatch_async到它自己的类

+ (void)dispatchOnMainQueue:(Block)block
{
    if ([NSThread currentThread] == [NSThread mainThread]) {
        block();
    } else {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

+ (void)dispatchOnBackgroundQueue:(Block)block
{
    dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_async(background, block);
}

然后在测试执行期间,调动后台调度以在主队列上发生。这行得通,但无法预测。也觉得很不对劲!


2) 将设置代码移动到Kiwi'beforeAll块,然后休眠主线程。这是因为Kiwi测试是在主线程上运行的,所以我们实际上是在说“让后台操作在继续测试之前发生”。我想这就是我要使用的。是的,它使我的单元测试运行得更慢,但是它们在应该做的时候通过,在应该做的时候失败

describe(@"MyAction", ^{

    __block void (^completionBlock)(NSArray *array, NSError *error);

    beforeAll(^{
        // Stub the http client
        id mockClient = [AFHTTPClient mock];
        [WRNAPIClient stub:@selector(sharedClient) andReturn:mockClient];

        // capture the block argument
        KWCaptureSpy *spy = [mockClient captureArgument:@selector(fetchAllThingsWithCompletion:) atIndex:0];
        [WRNTaskImporter importAllTasksFromAPI];
        completionBlock = spy.argument;

        // run the completion block 
        completionBlock(@[@"blah"], nil);

        // Wait for background import to complete
        [NSThread sleepForTimeInterval:0.1];

    })

    // This works 
    it(@"should return the right count", ^{
        // entityCount is a block that performs a fetch request count
        NSInteger count = entityCount(moc, @"Task");
        [[theValue(count) should] equal:theValue(4)];
    })
};

这种方法的警告是,它仅在您在测试前不更改任何数据时才有效。例如,我插入 4 个实体,并希望检查每个实体是否按预期插入。此选项将在这里工作。如果我需要重新运行导入方法并检查计数没有增加,我需要[NSThread sleepForTimeInterval:0.1]在调用插入代码后添加另一个。


对于基于块的普通Kiwi测试,您可能应该使用该expectFutureValue shouldEventually方法或KWCaptureSpy测试您的代码,但这在调用嵌套块时可能无济于事。

如果有人有更合适的方法来测试这样的案例,我很高兴听到它!

于 2014-02-07T09:12:34.347 回答