如何让 XCTest 在运行测试之前等待 setUp 中的异步调用?

问题描述 投票:0回答:5

我正在 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)
            }
        }

    }

}
xcode unit-testing swift asynchronous xctest
5个回答
127
投票

您可以使用在异步测试用例中使用的相同

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];
}

35
投票

运行异步测试有两种技术。

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
变量进行一些同步)。


5
投票

斯威夫特 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 `asyncAfter` called
    }
}

和这样的用法:

func testShouldDeleteSection() {
        let tableView = TableViewSpy()
        sut.tableView = tableView
        
        sut.sectionDidDelete(at: 0)
        
        wait {
            XCTAssert(tableView.isReloadDataCalled, "Check reload table view after section delete")
        }
    }

上面的例子不完整,但是你可以明白。希望这会有所帮助。


0
投票

Swift 5.5 和 iOS 13+

你可以覆盖

func setUp() async throws
例如:

final class MyTestsAsync: XCTestCase {
    var mockService: ServiceProtocolMock!

    override func setUp() async throws {
        mockService = await {
            //... some async setup
        }()
    }

    override func tearDown() async throws {
        //...

有关并发注释的 Apple 文档在这里


0
投票

现代异步/等待

你也可以使用

Task

override func setUp() async throws {
    try await super.setUp()

    let task = Task {
        try await loadDataLibrary() // async function to preload data for tests
    }
    
    try await task.value

    // more setup here...
}
© www.soinside.com 2019 - 2024. All rights reserved.