मेरी उपयोगिता रननीय या कॉल करने योग्य निष्पादन को दोहराती है जब तक कि यह त्रुटियों के बिना गुजरता है या टाइमआउट के बाद फेंकने योग्य नहीं है। यह एस्प्रेसो परीक्षणों के लिए पूरी तरह से काम करता है!
मान लीजिए कि अंतिम दृश्य इंटरैक्शन (बटन क्लिक) कुछ पृष्ठभूमि थ्रेड्स (नेटवर्क, डेटाबेस आदि) को सक्रिय करता है। परिणामस्वरूप, एक नई स्क्रीन दिखाई देनी चाहिए और हम इसे अपने अगले चरण में जांचना चाहते हैं, लेकिन हमें नहीं पता कि नई स्क्रीन कब परीक्षण के लिए तैयार होगी।
अनुशंसित दृष्टिकोण आपके ऐप को आपके परीक्षण के लिए थ्रेड राज्यों के बारे में संदेश भेजने के लिए बाध्य करने के लिए है। कभी-कभी हम अंतर्निहित तंत्रों जैसे OkHttp3IdlingResource का उपयोग कर सकते हैं। अन्य मामलों में, आपको केवल परीक्षण समर्थन के लिए अपने ऐप स्रोतों के विभिन्न स्थानों में कोड के टुकड़े डालने चाहिए (आपको ऐप लॉजिक जानना चाहिए!)। इसके अलावा, हमें आपके सभी एनिमेशन बंद कर देने चाहिए (हालाँकि यह UI का हिस्सा है)।
अन्य दृष्टिकोण प्रतीक्षा कर रहा है, उदाहरण के लिए 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