Xcode UIテストのテストケースの遅延/待機


182

Xcode 7ベータ2で利用可能な新しいUI Testingを使用してテストケースを作成しようとしています。アプリにはログイン画面があり、サーバーに呼び出してログインします。これは非同期操作であるため、これに関連する遅延があります。

次の手順に進む前にXCTestCaseで遅延または待機メカニズムを引き起こす方法はありますか?

適切なドキュメントがないため、クラスのヘッダーファイルを確認しました。これに関連するものを見つけることができませんでした。

アイデア/提案はありますか?


13
私が考えてNSThread.sleepForTimeInterval(1)動作するはずです
Kametrixom

すごい!これは動作するようです。しかし、それが推奨される方法かどうかはわかりません。私はAppleがもっと良い方法を提供すべきだと思います。レーダーを提出する必要があるかもしれません
Tejas HS

私は実際にそれは大丈夫だと思います、それは現在のスレッドを特定の時間一時停止するための本当に最も一般的な方法です。さらに制御したい場合はdispatch_after、GCD(The 、dispatch_queuestuff)
Kametrixom

@Kametrixom実行ループをチェックしないでください-AppleはBeta 4でネイティブ非同期テストを導入しました。詳細については、私の回答を参照してください。
Joe Masilotti、2015

2
Swift 4.0-> Thread.sleep(forTimeInterval:2)
uplearnedu.com

回答:


168

非同期UIテストはXcode 7 Beta 4で導入されました。「Hello、world!」というテキストが付いたラベルを待つ 表示するには、次の操作を実行できます。

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

その他のUIテストについての詳細は、私のブログで見つけることができます。


19
残念ながら、タイムアウトが発生したことを受け入れて次に進む方法はありません- waitForExpectationsWithTimeout自動的にテストに失敗しますが、これは非常に残念です。
Jedidja 2015

@Jedidja実際、これはXCode 7.0.1では起こりません。
Bastian

@バスティアンうーん興味深い。これを再確認する必要があります。
Jedidja、2015年

1
私にはうまくいきません。これが私のサンプルです:let xButton = app.toolbars.buttons ["X"] let exists = NSPredicate(format: "exists == 1")expectationForPredicate(exists、EvaluationWithObject:xButton、handler:nil)waitForExpectationsWithTimeout(10、handler: nil)
emoleumassi 2016年

app.launch()アプリを再起動するだけのようです。必要ですか?
クリスプリンス

225

さらに、あなたはただ眠ることができます:

sleep(10)

UITestは別のプロセスで実行されるため、これは機能します。どれだけお勧めできるかわかりませんが、うまくいきます。


2
遅れて失敗を起こしたくない場合があります。感謝
Tai Le

13
私が今まで見た中で最高の答え:)私は:)ことができれば、私は+ 100票を追加します
パルトロミエSemańczyk

8
1秒未満の遅延を指定できるので、NSThread.sleepForTimeInterval(0.2)が好きです。(sleep()は整数パラメーターを取ります。1秒の倍数のみが可能です)。
Graham Perks

5
@GrahamPerks、はい、ありますが、usleep
mxcl

3
これは不十分な提案ではありませんが(UITestingがどのように機能するかはわかりません)、たとえそれが不十分な提案であったとしても、機能する期待(システムがだれに警告するか)を作成する方法がない場合があるため、これで十分です。
mxcl 2017年

78

iOS 11 / Xcode 9

<#yourElement#>.waitForExistence(timeout: 5)

これは、このサイトのすべてのカスタム実装の優れた代替品です。

https://stackoverflow.com/a/48937714/971329で私の回答を必ず確認してください。そこで、テストを実行する時間を大幅に短縮する、リクエストを待つ代わりの方法について説明します。


おかげで、私は、テキスト:)変更@daidai
blackjacx

1
ええ、これはまだ私が使っているアプローチでありXCTestCase、魅力のように機能します。sleep(3)テスト時間を人為的に延長し、テストスイートが大きくなると実際には選択肢がないため、ここでのようなアプローチが非常に高い投票数になる理由がわかりません。
blackjacx

実際にはXcode 9が必要ですが、iOS 10を実行しているデバイス/シミュレータでも機能します;-)
d4Rk

ええ、私は上の見出しにそれを書きました。しかし、今ではほとんどの人は、少なくともXcodeの9にアップグレードしている必要があります;-)
blackjacx

77

Xcode 9XCTWaiterで新しいトリックを導入しました

テストケースは明示的に待機します

wait(for: [documentExpectation], timeout: 10)

テストするウェイターインスタンスデリゲート

XCTWaiter(delegate: self).wait(for: [documentExpectation], timeout: 10)

ウェイタークラスが結果を返す

let result = XCTWaiter.wait(for: [documentExpectation], timeout: 10)
switch(result) {
case .completed:
    //all expectations were fulfilled before timeout!
case .timedOut:
    //timed out before all of its expectations were fulfilled
case .incorrectOrder:
    //expectations were not fulfilled in the required order
case .invertedFulfillment:
    //an inverted expectation was fulfilled
case .interrupted:
    //waiter was interrupted before completed or timedOut
}

使用例

Xcode 9より前

目的C

- (void)waitForElementToAppear:(XCUIElement *)element withTimeout:(NSTimeInterval)timeout
{
    NSUInteger line = __LINE__;
    NSString *file = [NSString stringWithUTF8String:__FILE__];
    NSPredicate *existsPredicate = [NSPredicate predicateWithFormat:@"exists == true"];

    [self expectationForPredicate:existsPredicate evaluatedWithObject:element handler:nil];

    [self waitForExpectationsWithTimeout:timeout handler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSString *message = [NSString stringWithFormat:@"Failed to find %@ after %f seconds",element,timeout];
            [self recordFailureWithDescription:message inFile:file atLine:line expected:YES];
        }
    }];
}

使用法

XCUIElement *element = app.staticTexts["Name of your element"];
[self waitForElementToAppear:element withTimeout:5];

迅速

func waitForElementToAppear(element: XCUIElement, timeout: NSTimeInterval = 5,  file: String = #file, line: UInt = #line) {
        let existsPredicate = NSPredicate(format: "exists == true")

        expectationForPredicate(existsPredicate,
                evaluatedWithObject: element, handler: nil)

        waitForExpectationsWithTimeout(timeout) { (error) -> Void in
            if (error != nil) {
                let message = "Failed to find \(element) after \(timeout) seconds."
                self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
            }
        }
    }

使用法

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element)

または

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element, timeout: 10)

ソース


1
上記のxcode9の例に関するいくつかの図を探しています
rd_


1
テスト済み。魅力的な作品!ありがとう!
Dawid Koncewicz 2017

32

Xcode 8.3以降では、http://masilotti.com/xctest-waiting/を使用できますXCTWaiter

func waitForElementToAppear(_ element: XCUIElement) -> Bool {
    let predicate = NSPredicate(format: "exists == true")
    let expectation = expectation(for: predicate, evaluatedWith: element, 
                                  handler: nil)

    let result = XCTWaiter().wait(for: [expectation], timeout: 5)
    return result == .completed
}

別のトリックはwait関数を書くことです、クレジットは私にそれを見せてくれたジョン・サンデルにあります

extension XCTestCase {

  func wait(for duration: TimeInterval) {
    let waitExpectation = expectation(description: "Waiting")

    let when = DispatchTime.now() + duration
    DispatchQueue.main.asyncAfter(deadline: when) {
      waitExpectation.fulfill()
    }

    // We use a buffer here to avoid flakiness with Timer on CI
    waitForExpectations(timeout: duration + 0.5)
  }
}

そしてそれを

func testOpenLink() {
  let delegate = UIApplication.shared.delegate as! AppDelegate
  let route = RouteMock()
  UIApplication.shared.open(linkUrl, options: [:], completionHandler: nil)

  wait(for: 1)

  XCTAssertNotNil(route.location)
}

11

@Tedの回答に基づいて、私はこの拡張機能を使用しました:

extension XCTestCase {

    // Based on https://stackoverflow.com/a/33855219
    func waitFor<T>(object: T, timeout: TimeInterval = 5, file: String = #file, line: UInt = #line, expectationPredicate: @escaping (T) -> Bool) {
        let predicate = NSPredicate { obj, _ in
            expectationPredicate(obj as! T)
        }
        expectation(for: predicate, evaluatedWith: object, handler: nil)

        waitForExpectations(timeout: timeout) { error in
            if (error != nil) {
                let message = "Failed to fulful expectation block for \(object) after \(timeout) seconds."
                self.recordFailure(withDescription: message, inFile: file, atLine: line, expected: true)
            }
        }
    }

}

こんな風に使えます

let element = app.staticTexts["Name of your element"]
waitFor(object: element) { $0.exists }

また、要素が消えるのを待つ、または他のプロパティが変更されるのを待つことができます(適切なブロックを使用することにより)

waitFor(object: element) { !$0.exists } // Wait for it to disappear

1非常にswifty、およびなどXCUIElements上のいくつかのプロパティを待っているときには、例えば、私は、標準的な述語表現は時々私のために仕事をしなかったため、多くの方が良いと思うブロック述語を使用しています
lawicko

10

編集:

Xcode 7b4では、UIテストで expectationForPredicate:evaluatedWithObject:handler:

元の:

もう1つの方法は、実行ループを一定時間スピンさせることです。待機する必要がある(推定)時間を知っている場合にのみ本当に役立ちます

Obj-C: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: <<time to wait in seconds>>]]

迅速: NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: <<time to wait in seconds>>))

テストを続行するためにいくつかの条件をテストする必要がある場合、これはあまり役に立ちません。条件付きチェックを実行するには、whileループを使用します。


これはクリーンで、特にアプリの起動を待つ、プリロードされたデータをリクエストする、ログイン/ログアウトなどを行うのにとても便利です。ありがとうございました。
felixwcf

4

次のコードはObjective Cでのみ機能します。

- (void)wait:(NSUInteger)interval {

    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:interval handler:nil];
}

以下のようにこの関数を呼び出すだけです。

[self wait: 10];

エラー->「NSInternalInconsistencyException」、「API違反-予期せずに待機するための呼び出しが行われました」をキャッチしました。
FlowUI。SimpleUITesting.com

@ iOSCalendarpatchthecode.com、その代替ソリューションを見つけましたか?
最大

@Maxあなたはこのページの他のどれでも使用できますか?
FlowUI。SimpleUITesting.com

@ iOSCalendarpatchthecode.comいいえ、チェックする要素がないため、遅延が必要です。だから私はこれの代わりが必要です。
最大

@Max iは、このページで選択した回答を使用しました。それは私のために働いた。たぶん、具体的に何を探しているのか尋ねることができます。
FlowUI。SimpleUITesting.com

4

私の場合、sleep副作用が発生したので使用しましたwait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)

0

XCUIElementのAPIを.exists使用すると、クエリが存在するかどうかを確認できるため、次の構文が役立つ場合があります。

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
while !label.exists {
    sleep(1)
}

最終的に期待に応えられると確信している場合は、これを実行してみてください。待機が長すぎる場合はクラッシュが望ましいかもしれません。その場合waitForExpectationsWithTimeout(_,handler:_)は@Joe Masilottiの投稿を使用する必要があります。


0

睡眠はスレッドをブロックします

「スレッドがブロックされている間、実行ループ処理は発生しません。」

あなたはwaitForExistenceを使用することができます

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}

0

これにより、スレッドをスリープ状態にしたり、タイムアウト時にエラーをスローしたりせずに、遅延が発生します。

let delayExpectation = XCTestExpectation()
delayExpectation.isInverted = true
wait(for: [delayExpectation], timeout: 5)

期待が逆転するため、静かにタイムアウトします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.