web-dev-qa-db-ja.com

テストを実行する前にXCTestにsetUpで非同期呼び出しを待機させるにはどうすればよいですか?

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)
            }
        }

    }

}
46
Brett Elliot

非同期テストを実行するには、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変数の同期を行う必要があります)。

22
Rob

セマフォまたはブロックループを使用する代わりに、同じ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];
}
94
RndmTsk

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")
        }
    }

上記の例は完全ではありませんが、アイデアを得ることができます。この助けを願っています。

1
moraei