用猕猴桃测试核心数据实体的背景



我正在努力找出测试与背景线程中核心数据交互的最佳方法。我有以下类方法:

+ (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 = //...
                }
            });
        }
    }];
}

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

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_async时,我可以在调用sleep(1)时运行的唯一方法是调用CC_3。使用sleep()使我认为我没有以正确的方式接近它。我尝试使用 shouldEventually,但这似乎并没有重新提高我的 count值。

您是否尝试过这些异步块宏?

#define TestNeedsToWaitForBlock() __block BOOL blockFinished = NO
#define BlockFinished() blockFinished = YES
#define WaitForBlock() while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !blockFinished)

我尝试了几种解决此问题的方法,没有一个感觉。


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)将设置代码移至KiwibeforeAll块,然后睡觉。背景操作发生在进行测试之前。我认为这就是我要使用的。是的,这会使我的单位测试运行较慢,但是当他们应该这样做时,它们会通过,并且在应该

时失败
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来测试您的代码,但这在调用嵌套块时可能无济于事。

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

最新更新