如何让 XCTest 在测试 运行 之前等待 setUp 中的异步调用?
How can I get XCTest to wait for async calls in setUp before tests are run?
我正在 Xcode 6 中编写集成测试,以配合我的单元和功能测试。 XCTest 有一个 setUp() 方法,在每次测试之前都会调用它。伟大的!
它还有 XCTestException,让我可以编写异步测试。也很棒!
但是,我想在每次测试之前用测试数据填充我的测试数据库,并且 setUp 只是在异步数据库调用完成之前开始执行测试。
有没有办法让 setUp 等到我的数据库准备就绪后再运行测试?
这是我现在所做的一个例子。由于在数据库完成填充之前设置 returns 我必须在每次测试时复制大量测试代码:
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)
}
}
}
}
运行异步测试有两种技术。 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
块在主队列上运行s,你可以使用运行循环:
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")
}
}
上面的示例不完整,但您可以理解。希望对您有所帮助。
我正在 Xcode 6 中编写集成测试,以配合我的单元和功能测试。 XCTest 有一个 setUp() 方法,在每次测试之前都会调用它。伟大的!
它还有 XCTestException,让我可以编写异步测试。也很棒!
但是,我想在每次测试之前用测试数据填充我的测试数据库,并且 setUp 只是在异步数据库调用完成之前开始执行测试。
有没有办法让 setUp 等到我的数据库准备就绪后再运行测试?
这是我现在所做的一个例子。由于在数据库完成填充之前设置 returns 我必须在每次测试时复制大量测试代码:
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)
}
}
}
}
运行异步测试有两种技术。 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
块在主队列上运行s,你可以使用运行循环:
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")
}
}
上面的示例不完整,但您可以理解。希望对您有所帮助。