auto_closureを利用して条件成立を待つXCTestExpectationをつくる
AppleのSwift Blogを読んで@auto_closure
が面白そうだと思い活用方法を考えてみました。
@auto_closure
はclosureを取る引数につけると、呼び出し側はclosureの{}
を省略できるようになるというものです。
Swift Blogによると、AppleはSwiftでのassert()
などの実装にこれを利用しているそうです。
今回はこれを利用して、条件が成立するまで待つXCTestExpectationを実装してみました。
以下のように使うことができます。
func testBlockOperation() {
let queue = NSOperationQueue()
let operation = NSBlockOperation(block: {
NSThread.sleepForTimeInterval(1.0)
})
queue.addOperation(operation)
expectationWithCondition(operation.finished)
waitForExpectationsWithTimeout(10.0, handler: nil)
}
expectationWithCondition:
が今回実装したメソッドです。
この例ではoperation.finished == true
となるまで待つというexpectationがつくられています。
引数は@auto_closure
になっているので、operation.finished
はclosure { operation.finished }
として渡されます。
expectationWithCondition:
の実装は以下のようになっており、渡されたclosureを反復して実行します。
closureがtrueを返した場合にはexpectationをfulfillし、falseを返した場合には反復実行を継続します。
import Foundation
import XCTest
extension XCTestCase {
func iterateTestingCondition(condition: @auto_closure () -> LogicValue, expectation: XCTestExpectation?) {
weak var weakExpectation = expectation
if condition().getLogicValue() {
weakExpectation?.fulfill()
} else if weakExpectation {
let delay = 0.05 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
let queue = dispatch_get_main_queue()
dispatch_after(time, queue) {
self.iterateTestingCondition(condition, expectation: weakExpectation)
}
}
}
func expectationWithCondition(condition: @auto_closure () -> LogicValue) -> XCTestExpectation {
let expectation = expectationWithDescription("expectationWithCondition:")
iterateTestingCondition(condition, expectation: expectation);
return expectation
}
}
テストケースの終了後にも反復実行が続いてしまうと困るので、expectationが生存しているかどうかで反復実行を続けるか判断します。
iterateTestingCondition:
内ではexpectationを弱参照しているので、テストケースが終了すると
iterateTestingCondition:
内のexpectatioがnilとなり、メソッドの呼び出しが終了します。
反復実行は力技っぽくて嫌だなーと思いつつも、非同期処理の完了をcompletion handler, KVO, NSNotificationなどで受け取れることが難しい場合もあるので、そういうときに使えるかもしれません。