Моя утилита повторяет запускаемое или вызываемое выполнение до тех пор, пока не пройдет без ошибок или не выдаст throwable после тайм-аута. Он отлично подходит для тестов эспрессо!
Предположим, что последнее взаимодействие с просмотром (нажатие кнопки) активирует некоторые фоновые потоки (сеть, база данных и т. Д.). В результате должен появиться новый экран, и мы хотим проверить его на следующем шаге, но мы не знаем, когда новый экран будет готов для тестирования.
Рекомендуемый подход - заставить ваше приложение отправлять в тест сообщения о состояниях потоков. Иногда мы можем использовать встроенные механизмы, такие как OkHttp3IdlingResource. В других случаях вам следует вставлять фрагменты кода в разные места источников вашего приложения (вы должны знать логику приложения!) Только для поддержки тестирования. Более того, мы должны отключить все ваши анимации (хотя это часть пользовательского интерфейса).
Другой подход - ожидание, например SystemClock.sleep (10000). Но мы не знаем, сколько ждать, и даже большие задержки не могут гарантировать успеха. С другой стороны, ваш тест продлится долго.
Мой подход - добавить временное условие для просмотра взаимодействия. Например, мы проверяем, что новый экран должен появиться в течение 10000 мс (таймаут). Но мы не ждем и не проверяем его так быстро, как хотим (например, каждые 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