私のユーティリティは、エラーなしでパスするか、タイムアウト後にスロー可能になるまで、実行可能または呼び出し可能な実行を繰り返します。エスプレッソテストに最適です。
最後のビュー操作(ボタンクリック)がいくつかのバックグラウンドスレッド(ネットワーク、データベースなど)をアクティブ化するとします。その結果、新しい画面が表示され、次のステップで確認する必要がありますが、新しい画面をいつテストする準備ができるかはわかりません。
推奨されるアプローチは、アプリにスレッドの状態に関するメッセージをテストに送信させることです。OkHttp3IdlingResourceのような組み込みメカニズムを使用できる場合もあります。それ以外の場合は、テストのサポートのみを目的として、アプリのソースのさまざまな場所にコードを挿入する必要があります(アプリのロジックはわかっているはずです!)。さらに、すべてのアニメーションをオフにする必要があります(ただし、UIの一部です)。
もう1つのアプローチは待機中です(SystemClock.sleep(10000)など)。しかし、どれだけ待つかわからず、長い遅延でも成功を保証することはできません。一方、テストは長く続きます。
私のアプローチは、相互作用を表示するために時間条件を追加することです。たとえば、10000 mc(タイムアウト)の間に新しい画面が表示されることをテストします。しかし、私たちは待つことなく、必要なだけすばやくチェックします(たとえば100ミリ秒ごと)。もちろん、そのようにしてテストスレッドをブロックしますが、通常、このような場合に必要なのはそれだけです。
Usage:
long timeout=10000;
long matchDelay=100; //(check every 100 ms)
EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);
ViewInteraction loginButton = onView(withId(R.id.login_btn));
loginButton.perform(click());
myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));
これは私のクラスのソースです:
/**
* Created by alexshr on 02.05.2017.
*/
package com.skb.goodsapp;
import android.os.SystemClock;
import android.util.Log;
import java.util.Date;
import java.util.concurrent.Callable;
/**
* The utility repeats runnable or callable executing until it pass without errors or throws throwable after timeout.
* It works perfectly for Espresso tests.
* <p>
* Suppose the last view interaction (button click) activates some background threads (network, database etc.).
* As the result new screen should appear and we want to check it in our next step,
* but we don't know when new screen will be ready to be tested.
* <p>
* Recommended approach is to force your app to send messages about threads states to your test.
* Sometimes we can use built-in mechanisms like OkHttp3IdlingResource.
* In other cases you should insert code pieces in different places of your app sources (you should known app logic!) for testing support only.
* Moreover, we should turn off all your animations (although it's the part on ui).
* <p>
* The other approach is waiting, e.g. SystemClock.sleep(10000). But we don't known how long to wait and even long delays can't guarantee success.
* On the other hand your test will last long.
* <p>
* My approach is to add time condition to view interaction. E.g. we test that new screen should appear during 10000 mc (timeout).
* But we don't wait and check new screen as quickly as it appears.
* Of course, we block test thread such way, but usually it's just what we need in such cases.
* <p>
* Usage:
* <p>
* long timeout=10000;
* long matchDelay=100; //(check every 100 ms)
* EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);
* <p>
* ViewInteraction loginButton = onView(withId(R.id.login_btn));
* loginButton.perform(click());
* <p>
* myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));
*/
public class EspressoExecutor<T> {
private static String LOG = EspressoExecutor.class.getSimpleName();
public static long REPEAT_DELAY_DEFAULT = 100;
public static long BEFORE_DELAY_DEFAULT = 0;
private long mRepeatDelay;//delay between attempts
private long mBeforeDelay;//to start attempts after this initial delay only
private long mTimeout;//timeout for view interaction
private T mResult;
/**
* @param timeout timeout for view interaction
* @param repeatDelay - delay between executing attempts
* @param beforeDelay - to start executing attempts after this delay only
*/
public EspressoExecutor(long timeout, long repeatDelay, long beforeDelay) {
mRepeatDelay = repeatDelay;
mBeforeDelay = beforeDelay;
mTimeout = timeout;
Log.d(LOG, "created timeout=" + timeout + " repeatDelay=" + repeatDelay + " beforeDelay=" + beforeDelay);
}
public EspressoExecutor(long timeout, long repeatDelay) {
this(timeout, repeatDelay, BEFORE_DELAY_DEFAULT);
}
public EspressoExecutor(long timeout) {
this(timeout, REPEAT_DELAY_DEFAULT);
}
/**
* call with result
*
* @param callable
* @return callable result
* or throws RuntimeException (test failure)
*/
public T call(Callable<T> callable) {
call(callable, null);
return mResult;
}
/**
* call without result
*
* @param runnable
* @return void
* or throws RuntimeException (test failure)
*/
public void call(Runnable runnable) {
call(runnable, null);
}
private void call(Object obj, Long initialTime) {
try {
if (initialTime == null) {
initialTime = new Date().getTime();
Log.d(LOG, "sleep delay= " + mBeforeDelay);
SystemClock.sleep(mBeforeDelay);
}
if (obj instanceof Callable) {
Log.d(LOG, "call callable");
mResult = ((Callable<T>) obj).call();
} else {
Log.d(LOG, "call runnable");
((Runnable) obj).run();
}
} catch (Throwable e) {
long remain = new Date().getTime() - initialTime;
Log.d(LOG, "remain time= " + remain);
if (remain > mTimeout) {
throw new RuntimeException(e);
} else {
Log.d(LOG, "sleep delay= " + mRepeatDelay);
SystemClock.sleep(mRepeatDelay);
call(obj, initialTime);
}
}
}
}
https://gist.github.com/alexshr/ca90212e49e74eb201fbc976255b47e0