Snow Leopardあたりから登場したObjective-C Blocks(ブロック)、非同期処理に大活躍してますよね。ネットワーク周りの処理なんかはこれなしではやってられないと言っても過言ではないと思います。
しかしこのブロック(ていうか非同期処理全般だけど)、OCUnit(SenTesting)でなかなかテストしづらいんですよね。たとえば次のテストを実行したとします。
- (void)testAsyncBlock {
[foo processSomeTasksWithCompletionHandler:^(id result) {
STAssertNotNil(@"(アカン)");
}];
}
このテスト、非同期処理の結果を受け取るブロックのなかでその結果がnilかどうかをテストしたいわけですが、残念ながらうまくいきません。非同期処理が終了する前にテストが終了してしまうので、resultの内容によらずにテスト全パスです。これじゃ困るので、非同期処理が終わるまでテストを終わらせないようにします。
- (void)testAsyncBlock {
__block isFinished = NO;
[foo processSomeTasksWithCompletionHandler:^(id result) {
STAssertNotNil(@"(アカン)");
isFinished = YES;
}];
while (!isFinished) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}
}
終了したかどうかを判定するためにフラグを用意するという非常に原始的な方法ですが、これで一応ちゃんとテストできるようになりました。ただ、これを毎回書くのは面倒なので、ちょっとだけ楽にするマクロを書きました。
- (void)testAsyncBlock {
STAsynchronousTestStart(test1);
[foo processSomeTasksWithCompletionHandler:^(id result) {
STAssertNotNil(@"(アカン)");
STAsynchronousTestDone(test1);
}];
STAsynchronousTestWait(test1, 0.5);
}
// ネストしたブロック
- (void)testAnotherAsyncBlock {
STAsynchronousTestStart(test2);
[foo processOtherTasksWithCompletionHandler:^(NSArray *array) {
STAsynchronousTestSetCount(test2, [array count]);
for (id obj in array) {
[bar processObj:obj completionHandler:^(id result) {
STAssertNotNil(@"いかんのか?");
STAsynchronousTestDone(test2);
}];
}
}];
STAsynchronousTestWait(test2, 0.5);
}
あまり変わらない?ほんのちょっとだけコードが見やすくなったような気がしますよ。