非同期テストにXcode6(ベータ5)のXCTestExpectationsを使用しています。すべての非同期テストは、実行するたびに個別に合格します。ただし、スイート全体を実行しようとすると、一部のテストに合格せず、アプリがクラッシュします。
私が得るエラーは言うことですAPI violation - multiple calls made to -[XCTestExpectation fulfill]
。実際、これは単一の方法では当てはまりません。テストの一般的な形式を以下に示します。
- (void) someTest {
/* Declare Expectation */
XCTestExpectation *expectation = [self expectationWithDescription:@"My Expectation"];
[MyClass loginOnServerWithEmail:@"[email protected]" andPassword:@"asdfasdf" onSuccess:^void(User *user) {
/* Make some assertions here about the object that was given. */
/* Fulfill the expectation */
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
/* Error handling here */
}];
}
繰り返しますが、これらのテストは個別に実行すると合格し、実際にはネットワーク要求を行っています(意図したとおりに機能しています)が、一緒にすると、テストのコレクションは実行できません。
私はこの投稿を見ることができました ここ 、しかし私のために働く解決策を得ることができませんでした。
さらに、OSX Mavericksを実行していて、Xcode 6(ベータ5)を使用しています。
__weak
または__block
を使用することは良いアプローチではないと思います。私はしばらくの間XCTestExpectation
を使用して多くの単体テストを作成しましたが、これまでこの問題が発生したことはありません。私はついに、アプリにバグを引き起こす可能性のある問題の本当の原因を発見しました。私の問題の根本的な原因は、startAsynchronousTaskWithDuration
がcompletionHandlerを複数回呼び出すことです。私がそれを修正した後、API違反はなくなりました!
[self startAsynchronousTaskWithDuration:4 completionHandler:^(id result, NSError *error) {
XCTAssertNotNil(result);
XCTAssertNil(error);
[expectation fulfill];
}];
単体テストを修正するのに数時間かかりましたが、アプリの将来のランタイムの問題を回避するのに役立つAPI違反エラーに感謝するようになりました。
キャッチされなかった例外「NSInternalInconsistencyException」が原因でアプリを終了しています。理由:「API違反-[XCTestExpectationfulfill]に対して複数の呼び出しが行われました。」
次のコードで上記のエラーが発生します:
func testMultipleWaits() {
let exp = expectation(description: "whatever")
for _ in 0...10 {
DispatchQueue.main.async {
exp.fulfill()
}
wait(for: [exp], timeout: 1)
}
}
次の変更を意味しても、それを修正することはできません。それは、それを何度も待っているからです。次のエラーが発生します。
失敗:「NSInternalInconsistencyException」をキャッチ、「API違反-期待値は1回だけ待機でき、
whatever
はすでに待機しています」
func testMultipleWaits() {
let exp = expectation(description: "whatever")
for i in 0...10 {
DispatchQueue.main.async {
if i == 6 {
exp.fulfill()
}
}
wait(for: [exp], timeout: 1)
}
}
混乱を招くのは、上記の変更がまだクラッシュしているにもかかわらず、テストが終了し、「非同期待機に失敗しました:タイムアウトが1秒を超えましたが、期待が満たされていない:「何でも」」という失敗が発生することです。
根本的な原因であるクラッシュの修正に時間を費やす必要がある一方で、テストの修正に時間を費やす可能性があるため、誤解を招く恐れがあります。
ここでの修正は、期待値inside forループを設定します。このようにして、期待が満たされ、期待ごとに1回待機されます。
func testMultipleWaits() {
for _ in 0...10 {
let exp = expectation(description: "whatever")
DispatchQueue.main.async {
exp.fulfill()
}
wait(for: [exp], timeout: 1)
}
}
期待が複数回満たされるブロックを呼び出しているオブジェクトを解放できない場所で、保持サイクルの問題が発生している可能性があります。
それ以外の場合、期待値が複数回呼び出されることが期待される動作である場合は、期待値の数を指定できる小さな拡張機能を作成しました。
import XCTest
extension XCTestExpectation {
private class IntWrapper {
let value :Int!
init?(value:Int?) {
self.value = value
if (value == nil) {
return nil
}
}
}
private struct AssociatedKey {
static var expectationCountKey = ""
}
var expectationCount:Int? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.expectationCountKey) as? Int
}
set {
objc_setAssociatedObject(self, &AssociatedKey.expectationCountKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func decrementalFulfill() {
guard let expectationCount = self.expectationCount else {
fulfill()
return
}
self.expectationCount = expectationCount - 1
if self.expectationCount <= 0 {
fulfill()
}
}
}
完全なコード(テストあり:)ここ: https://Gist.github.com/huguesbr/7d110bffd043e4d11f2886693c680b06