我正在努力找出测试与背景线程中核心数据交互的最佳方法。我有以下类方法:
+ (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)将设置代码移至Kiwi
的beforeAll
块,然后睡觉。背景操作发生在进行测试之前。我认为这就是我要使用的。是的,这会使我的单位测试运行较慢,但是当他们应该这样做时,它们会通过,并且在应该
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
来测试您的代码,但这在调用嵌套块时可能无济于事。
如果有人有更合适的测试方法,我很高兴听到它!