Как я могу заставить XCTest ждать асинхронных вызовов в setUp перед запуском тестов?

Я пишу интеграционные тесты в Xcode 6, чтобы идти вместе с моими модульными и функциональными тестами. XCTest имеет метод setUp (), который вызывается перед каждым тестом. Здорово!

Он также имеет XCTestException, которые позволяют мне писать асинхронные тесты. Тоже здорово!

тем не менее, я хотел бы заполнить мою тестовую базу данных тестовыми данными перед каждым тестом и установкой просто начинает выполнять тесты до вызова асинхронной базы данных.

есть ли способ настроить ожидание, пока моя база данных готова до запуска тестов?

вот пример того, что я делаю сейчас. Поскольку 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 = dispatch_semaphore_create(0)

    db.overwriteDatabase(data) {

        // do some stuff

        dispatch_semaphore_signal(semaphore)
    }

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}

обратите внимание, для того чтобы работать, этот 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 {
        NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture())
    }
}

это очень неэффективный шаблон, но в зависимости от как!--6--> было реализовано, это может быть необходимо

Примечание используйте только этот шаблон, если вы знаете, что 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];
}