J'écris des tests d'intégration dans Xcode 6 pour accompagner mes tests unitaires et fonctionnels. XCTest a une méthode setUp () qui est appelée avant chaque test. Génial!
Il a également des XCTestException qui me permettent d'écrire des tests asynchrones. Aussi génial!
Cependant, je voudrais remplir ma base de données de test avec des données de test avant chaque test et setUp commence juste à exécuter des tests avant que l'appel de base de données asynchrone ne soit effectué.
Existe-t-il un moyen de faire configurer SetUp jusqu'à ce que ma base de données soit prête avant d'exécuter des tests?
Voici un exemple de ce que je fais maintenant. Puisque setUp revient avant que la base de données ne soit remplie, je dois dupliquer beaucoup de code de test à chaque test:
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)
}
}
}
Voici ce que j'aimerais:
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)
}
}
}
}
Il existe deux techniques pour exécuter des tests asynchrones. XCTestExpectation
et sémaphores. Dans le cas de faire quelque chose d'asynchrone dans setUp
, vous devez utiliser la technique du sémaphore:
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()
}
Notez que pour que cela fonctionne, ce bloc onDone
ne peut pas s'exécuter sur le thread principal (sinon vous bloquerez).
Si ce bloc onDone
s'exécute sur la file d'attente principale, vous pouvez utiliser des boucles d'exécution:
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)
}
}
Il s'agit d'un modèle très inefficace, mais selon la façon dont overwriteDatabase
a été implémenté, il peut être nécessaire
Remarque, n'utilisez ce modèle que si vous savez que le bloc onDone
s'exécute sur le thread principal (sinon vous devrez synchroniser la variable finished
).
Plutôt que d'utiliser des sémaphores ou des boucles de blocage, vous pouvez utiliser le même waitForExpectationsWithTimeout:handler:
fonction que vous utilisez dans vos cas de test asynchrones.
// 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
utilisez cette extension:
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
}
}
et une utilisation comme celle-ci:
func testShoudDeleteSection() {
let tableView = TableViewSpy()
sut.tableView = tableView
sut.sectionDidDelete(at: 0)
wait {
XCTAssert(tableView.isReloadDataCalled, "Chcek relaod table view after section delete")
}
}
l'exemple ci-dessus n'est pas complet mais vous pouvez vous faire une idée. j'espère que cette aide.