Xcode 6で統合テストを書いて、ユニットテストと機能テストと並行して進めています。 XCTestには、すべてのテストの前に呼び出されるsetUp()メソッドがあります。すばらしいです!
また、非同期テストを作成できるXCTestExceptionもあります。また素晴らしい!
ただし、すべてのテストの前にテストデータベースにテストデータを入力し、非同期データベースの呼び出しが完了する前にsetUpがテストの実行を開始したいだけです。
テストを実行する前に、データベースの準備が整うまでsetUpを待機させる方法はありますか?
ここに私が今していることの例があります。データベースの設定が完了する前にsetUpが返されるため、テストごとに多くのテストコードを複製する必要があります。
func test_checkSomethingExists() {
let expectation = expectationWithDescription("")
var expected:DatabaseItem
// Fill out a database with data.
var data = getData()
overwriteDatabase(data, {
// Database populated.
// Do test... in this pseudocode I just check something...
db.retrieveDatabaseItem({ expected in
XCTAssertNotNil(expected)
expectation.fulfill()
})
})
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
}
私が欲しいものは次のとおりです。
class MyTestCase: XCTestCase {
override func setUp() {
super.setUp()
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
var data = getData()
db.overwriteDatabase(data, onDone: () -> () {
// When database done, do something that causes setUp to end
// and start running tests
})
}
func test_checkSomethingExists() {
let expectation = expectationWithDescription("")
var expected:DatabaseItem
// Do test... in this pseudocode I just check something...
db.retrieveDatabaseItem({ expected in
XCTAssertNotNil(expected)
expectation.fulfill()
})
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
}
}
非同期テストを実行するには、2つの手法があります。 XCTestExpectation
およびセマフォ。 setUp
で非同期の処理を行う場合、セマフォ手法を使用する必要があります。
override func setUp() {
super.setUp()
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
let data = getData()
let semaphore = DispatchSemaphore(value: 0)
db.overwriteDatabase(data) {
// do some stuff
semaphore.signal()
}
semaphore.wait()
}
それが機能するために、このonDone
ブロックはメインスレッドで実行できないことに注意してください(または、デッドロックします)。
このonDone
ブロックがメインキューで実行される場合、実行ループを使用できます。
override func setUp() {
super.setUp()
var finished = false
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
let data = getData()
db.overwriteDatabase(data) {
// do some stuff
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
}
これは非常に非効率的なパターンですが、overwriteDatabase
の実装方法によっては必要になる場合があります
onDone
ブロックがメインスレッドで実行されることがわかっている場合にのみ、このパターンを使用してください(そうでない場合は、finished
変数の同期を行う必要があります)。
セマフォまたはブロックループを使用する代わりに、同じwaitForExpectationsWithTimeout:handler:
非同期テストケースで使用する関数。
// Swift
override func setUp() {
super.setUp()
let exp = expectation(description: "\(#function)\(#line)")
// Issue an async request
let data = getData()
db.overwriteDatabase(data) {
// do some stuff
exp.fulfill()
}
// Wait for the async request to complete
waitForExpectations(timeout: 40, handler: nil)
}
// Objective-C
- (void)setUp {
[super setUp];
NSString *description = [NSString stringWithFormat:@"%s%d", __FUNCTION__, __LINE__];
XCTestExpectation *exp = [self expectationWithDescription:description];
// Issue an async request
NSData *data = [self getData];
[db overwriteDatabaseData: data block: ^(){
[exp fulfill];
}];
// Wait for the async request to complete
[self waitForExpectationsWithTimeout:40 handler: nil];
}
Swift 4.2
この拡張機能を使用します。
import XCTest
extension XCTestCase {
func wait(interval: TimeInterval = 0.1 , completion: @escaping (() -> Void)) {
let exp = expectation(description: "")
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
completion()
exp.fulfill()
}
waitForExpectations(timeout: interval + 0.1) // add 0.1 for sure asyn after called
}
}
このような使用法:
func testShoudDeleteSection() {
let tableView = TableViewSpy()
sut.tableView = tableView
sut.sectionDidDelete(at: 0)
wait {
XCTAssert(tableView.isReloadDataCalled, "Chcek relaod table view after section delete")
}
}
上記の例は完全ではありませんが、アイデアを得ることができます。この助けを願っています。