意式浓缩咖啡:Thread.sleep();


102

Espresso声称不需要Thread.sleep();,但是除非包含它,否则我的代码将不起作用。我正在连接到IP。连接时,将显示一个进度对话框。我需要sleep等待对话框关闭。这是我在其中使用的测试代码段:

    IP.enterIP(); // fills out an IP dialog (this is done with espresso)

    //progress dialog is now shown
    Thread.sleep(1500);

    onView(withId(R.id.button).perform(click());

我曾尝试这个代码不用Thread.sleep();,但它说,R.id.Button不存在。我可以使它正常工作的唯一方法是睡觉。

另外,我尝试用Thread.sleep();类似的东西代替,getInstrumentation().waitForIdleSync();但仍然没有运气。

这是唯一的方法吗?还是我错过了什么?

提前致谢。


您是否有可能要放置不必要的While循环,而您想阻止呼叫。
kedark 2014年

好的..让我解释一下。为您提供2条建议1)实施类似回调的机制。在连接建立时调用一种方法并显示视图。2)您要在IP.enterIP()之间创建延迟;和onView(....),所以您可以放置​​while循环,这将产生类似的延迟调用onview(..)...,但我认为,如果可能的话,请选择选项1。(创建回调机制)...
kedark 2014年

@kedark是的,这是一种选择,但这是Espresso的解决方案吗?
乍得宾厄姆2014年

您的问题中有未回答的评论,您可以回答吗?
Bolhoso 2014年

@Bolhoso,什么问题?
乍得宾厄姆

Answers:


110

我认为正确的方法是:

/** Perform action of waiting for a specific view id. */
public static ViewAction waitId(final int viewId, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            uiController.loopMainThreadUntilIdle();
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + millis;
            final Matcher<View> viewMatcher = withId(viewId);

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    // found view with required ID
                    if (viewMatcher.matches(child)) {
                        return;
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}

然后使用方式将是:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));

3
感谢Alex,您为什么在IdlingResource或AsyncTasks上选择了此选项?
蒂姆·博兰德

1
这是一种变通方法,在大多数情况下Espresso可以毫无问题地完成工作,并且无需特殊的“等待代码”。我实际上尝试了几种不同的方法,并认为这是最匹配的Espresso体系结构/设计之一。
Oleksandr Kucherenko 2014年

1
@AlexK,这使我成为同伴!
dawid gdanski

1
对我来说,它对于api <= 19失败,在行上抛出new PerformException.Builder()
Prabin Timsina,2016年

4
我希望您了解它的示例,可以根据自己的需要复制/粘贴和修改。完全根据自己的业务需要(而不是我的需要)完全使用它是您的责任。
亚历山大·库切连科

47

感谢AlexK的出色回答。在某些情况下,您需要延迟一些代码。它不必等待服务器响应,而可以等待动画完成。我个人对Espresso idolingResources有问题(我想我们为简单的事情编写了许多行代码),所以我将AlexK的方式更改为以下代码:

/**
 * Perform action of waiting for a specific time.
 */
public static ViewAction waitFor(final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "Wait for " + millis + " milliseconds.";
        }

        @Override
        public void perform(UiController uiController, final View view) {
            uiController.loopMainThreadForAtLeast(millis);
        }
    };
}

因此您可以创建一个 Delay类并将此方法放入其中,以便轻松访问它。您可以以相同的方式在Test类中使用它:onView(isRoot()).perform(waitFor(5000));


7
甚至可以用以下一行代码简化perform方法:uiController.loopMainThreadForAtLeast(millis);
Yair Kukielka '16

太棒了,我不知道那件事:thumbs_up @YairKukielka
Hesam

忙着等待的Yikes。
TWiStErRob,2016年

太棒了 我一直在寻找那个。+1是等待问题的简单解决方案。
Tobias Reich

增加延迟而不是使用延迟的更好方法Thread.sleep()
Wahib Ul Haq

23

当我寻找一个类似问题的答案时,我偶然发现了这个线程,当时我正在等待服务器响应,并根据响应更改元素的可见性。

尽管上述解决方案肯定有帮助,但我最终从chiuki中找到了一个出色的例子,现在当我在应用程序空闲期间等待操作发生时,就将这种方法用作我的首选

我已经将ElapsedTimeIdlingResource()添加到我自己的实用程序类中,现在可以将其有效地用作特浓咖啡的替代品,并且使用起来很干净:

// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

// Now we wait
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

// Stop and verify
onView(withId(R.id.toggle_button))
    .check(matches(withText(R.string.stop)))
    .perform(click());
onView(withId(R.id.result))
    .check(matches(withText(success ? R.string.success: R.string.failure)));

// Clean up
Espresso.unregisterIdlingResources(idlingResource);

我得到一个 I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResource错误。任何想法。我使用Proguard,但禁用了混淆功能。
安东尼

尝试-keep为未找到的类添加一条语句,以确保ProGuard不会不必要地删除它们。此处的更多信息:developer.android.com/tools/help/proguard.html#keep-code
MattMatt

我发布了一个问题stackoverflow.com/questions/36859528/…。该类位于seed.txt和mapping.txt中
Anthony

2
如果您需要更改空闲策略,则可能没有正确实现空闲资源。从长远来看,最好花时间修复它。这种方法最终会导致测试缓慢且不稳定。退房google.github.io/android-testing-support-library/docs/espresso/...
何塞Alcérreca

你说得很对。这个答案已有一年多的历史了,从那以后,空闲资源的行为得到了改善,以至于我使用上面代码的用例现在可以立即使用,可以正确检测到模拟的API客户端-我们不再使用上面的代码因此,在我们的测试中使用ElapsedTimeIdlingResource。(当然,您也可以将所有内容接收到Rx,这消除了在等待时间内进行黑客攻击的需要)。也就是说,Google的服务方式并不总是最好的:philosophicalhacker.com/post/…
MattMatt

18

我认为添加这一行更容易:

SystemClock.sleep(1500);

在返回之前等待给定的毫秒数(uptimeMillis)。与sleep(long)类似,但不引发InterruptedException; 将interrupt()事件推迟到下一个可中断的操作。至少经过指定的毫秒数后才会返回。


Expresso避免这些导致片状测试的硬编码睡眠。如果是这样的话,我也可以选择像appium这样的黑匣子工具
Emjey,

6

您可以只使用Barista方法:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista是包装Espresso以避免添加已接受答案所需的所有代码的库。这是一个链接! https://github.com/SchibstedSpain/Barista


我和做线程睡眠没有区别
Pablo Caviglia

老实说,我不记得有人在Google的哪个视频中说过,我们应该使用这种方式进行睡眠而不是普通的睡眠Thread.sleep()。抱歉! 这是Google制作的有关Espresso的第一批视频,但我不记得是哪一个……是几年前。抱歉! :·)噢!编辑!我将链接链接到三年前打开的PR中的视频。看看这个!github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

这类似于此答案,但是使用超时而不是尝试,并且可以与其他ViewInteractions链接:

/**
 * Wait for view to be visible
 */
fun ViewInteraction.waitUntilVisible(timeout: Long): ViewInteraction {
    val startTime = System.currentTimeMillis()
    val endTime = startTime + timeout

    do {
        try {
            check(matches(isDisplayed()))
            return this
        } catch (e: NoMatchingViewException) {
            Thread.sleep(50)
        }
    } while (System.currentTimeMillis() < endTime)

    throw TimeoutException()
}

用法:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())

4

我是编码和Espresso的新手,所以虽然我知道好的和合理的解决方案是使用空闲,但我还不足够聪明。

在我变得更加有知识之前,我仍然需要测试以某种方式运行,因此,现在,我正在使用这个肮脏的解决方案,该解决方案进行了许多尝试来查找元素,如果找不到则停止,如果没有,则短暂地休眠并开始再次达到最大尝试次数nr(到目前为止,最大尝试次数约为150)。

private static boolean waitForElementUntilDisplayed(ViewInteraction element) {
    int i = 0;
    while (i++ < ATTEMPTS) {
        try {
            element.check(matches(isDisplayed()));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Thread.sleep(WAITING_TIME);
            } catch (Exception e1) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

我在通过ID,文本,父级等查找元素的所有方法中都使用此方法:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}

在您的示例中,该findById(int itemId)方法将返回一个元素(可能为NULL),无论waitForElementUntilDisplayed(element);返回的是true还是false .... so,那都不行
mbob

我只想插话说这是最好的解决方案。IdlingResource由于5秒的轮询速率粒度(对于我的用例而言太大了),对于我来说还不够。接受的答案对我也不起作用(该答案的详细评论供稿中已经包含了为什么的解释)。谢谢你!我接受了您的想法,并提出了自己的解决方案,它的工作原理很有吸引力。
oaskamay

是的,当要等待当前活动中未包含的元素时,这也是唯一对我有用的解决方案。
guilhermekrz

3

Espresso旨在避免在测试中调用sleep()。您的测试不应打开一个对话框来输入IP,而应该是被测试活动的责任。

另一方面,您的UI测试应:

  • 等待IP对话框出现
  • 填写IP地址,然后单击Enter
  • 等待您的按钮出现并单击它

该测试应如下所示:

// type the IP and press OK
onView (withId (R.id.dialog_ip_edit_text))
  .check (matches(isDisplayed()))
  .perform (typeText("IP-TO-BE-TYPED"));

onView (withText (R.string.dialog_ok_button_title))
  .check (matches(isDisplayed()))
  .perform (click());

// now, wait for the button and click it
onView (withId (R.id.button))
  .check (matches(isDisplayed()))
  .perform (click());

Espresso等待UI线程和AsyncTask池中正在发生的所有事情完成,然后再执行测试。

请记住,您的测试不应执行应用程序负责任的任何事情。它的行为应类似于“消息灵通的用户”:单击并验证屏幕上是否显示了某些内容的用户,但事实上,它知道组件的ID


2
您的示例代码与我在问题中编写的代码基本相同。
乍得宾厄姆2014年

@Binghammer我的意思是测试的行为应与用户的行为相同。也许我想念的是您的IP.enterIP()方法的作用。您可以编辑问题并加以澄清吗?
Bolhoso 2014年

我的评论说明了它的作用。这只是espresso中填写IP对话框的一种方法。都是UI。
乍得宾厄姆2014年

嗯...好吧,你是对的,我的测试基本上也是如此。您是否在UI线程或AsyncTasks之外执行了某些操作?
Bolhoso 2014年

16
Espresso不能像该答案的代码和文字所暗示的那样工作。对ViewInteraction的检查调用不会等待直到给定的Matcher成功,而是如果不满足条件立即失败。正确的方法是使用AsyncTasks(如本答案中所述),或者(如果无法实现)实现IdlingResource,该资源将在何时可以进行测试执行时通知Espresso的UiController。
haffax 2014年

2

您应该使用Espresso 闲置资源,此CodeLab建议使用

空闲资源表示异步操作,其结果会影响UI测试中的后续操作。通过向Espresso注册空闲资源,您可以在测试应用程序时更可靠地验证这些异步操作。

演示者异步调用的示例

@Override
public void loadNotes(boolean forceUpdate) {
   mNotesView.setProgressIndicator(true);
   if (forceUpdate) {
       mNotesRepository.refreshData();
   }

   // The network request might be handled in a different thread so make sure Espresso knows
   // that the app is busy until the response is handled.
   EspressoIdlingResource.increment(); // App is busy until further notice

   mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
       @Override
       public void onNotesLoaded(List<Note> notes) {
           EspressoIdlingResource.decrement(); // Set app as idle.
           mNotesView.setProgressIndicator(false);
           mNotesView.showNotes(notes);
       }
   });
}

依存关系

androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'

对于androidx

androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.2'

官方回购: https : //github.com/googlecodelabs/android-testing

IdlingResource示例: https //github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample


0

虽然我认为最好为此使用空闲资源(https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/),但您可以将其用作备用:

/**
 * Contains view interactions, view actions and view assertions which allow to set a timeout
 * for finding a view and performing an action/view assertion on it.
 * To be used instead of {@link Espresso}'s methods.
 * 
 * @author Piotr Zawadzki
 */
public class TimeoutEspresso {

    private static final int SLEEP_IN_A_LOOP_TIME = 50;

    private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10 * 1000L;

    /**
     * Use instead of {@link Espresso#onView(Matcher)}
     * @param timeoutInMillis timeout after which an error is thrown
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(long timeoutInMillis, @NonNull final Matcher<View> viewMatcher) {

        final long startTime = System.currentTimeMillis();
        final long endTime = startTime + timeoutInMillis;

        do {
            try {
                return new TimedViewInteraction(Espresso.onView(viewMatcher));
            } catch (NoMatchingViewException ex) {
                //ignore
            }

            SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
        }
        while (System.currentTimeMillis() < endTime);

        // timeout happens
        throw new PerformException.Builder()
                .withCause(new TimeoutException("Timeout occurred when trying to find: " + viewMatcher.toString()))
                .build();
    }

    /**
     * Use instead of {@link Espresso#onView(Matcher)}.
     * Same as {@link #onViewWithTimeout(long, Matcher)} but with the default timeout {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(@NonNull final Matcher<View> viewMatcher) {
        return onViewWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewMatcher);
    }

    /**
     * A wrapper around {@link ViewInteraction} which allows to set timeouts for view actions and assertions.
     */
    public static class TimedViewInteraction {

        private ViewInteraction wrappedViewInteraction;

        public TimedViewInteraction(ViewInteraction wrappedViewInteraction) {
            this.wrappedViewInteraction = wrappedViewInteraction;
        }

        /**
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction perform(final ViewAction... viewActions) {
            wrappedViewInteraction.perform(viewActions);
            return this;
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(final ViewAction... viewActions) {
            return performWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewActions);
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(long timeoutInMillis, final ViewAction... viewActions) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return perform(viewActions);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to perform view actions: " + viewActions))
                    .build();
        }

        /**
         * @see ViewInteraction#withFailureHandler(FailureHandler)
         */
        public TimedViewInteraction withFailureHandler(FailureHandler failureHandler) {
            wrappedViewInteraction.withFailureHandler(failureHandler);
            return this;
        }

        /**
         * @see ViewInteraction#inRoot(Matcher)
         */
        public TimedViewInteraction inRoot(Matcher<Root> rootMatcher) {
            wrappedViewInteraction.inRoot(rootMatcher);
            return this;
        }

        /**
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction check(final ViewAssertion viewAssert) {
            wrappedViewInteraction.check(viewAssert);
            return this;
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(final ViewAssertion viewAssert) {
            return checkWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewAssert);
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(long timeoutInMillis, final ViewAssertion viewAssert) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return check(viewAssert);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to check: " + viewAssert.toString()))
                    .build();
        }
    }
}

然后在您的代码中调用它,例如:

onViewWithTimeout(withId(R.id.button).perform(click());

代替

onView(withId(R.id.button).perform(click());

这还允许您为视图操作和视图断言添加超时。


在单行代码下面使用此代码来处理任何Test Espresso测试用例:SystemClock.sleep(1000); // 1秒
Nikunjkumar Kapupara

对我来说,这只能通过更改以下行return new TimedViewInteraction(Espresso.onView(viewMatcher));return new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
起作用

0

我的实用程序会重复执行可运行或可调用的执行,直到它无错误通过或在超时后抛出throwable为止。它非常适合Espresso测试!

假设最后一次视图交互(单击按钮)激活了一些后台线程(网络,数据库等)。结果,应该会出现一个新屏幕,我们要在下一步中进行检查,但是我们不知道何时可以准备测试新屏幕。

推荐的方法是强制您的应用向测试发送有关线程状态的消息。有时我们可以使用内置的机制,例如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


0

这是我在Kotlin中用于Android测试的帮助程序。就我而言,我正在使用longOperation来模仿服务器响应,但是您可以根据自己的目的进行调整。

@Test
fun ensureItemDetailIsCalledForRowClicked() {
    onView(withId(R.id.input_text))
        .perform(ViewActions.typeText(""), ViewActions.closeSoftKeyboard())
    onView(withId(R.id.search_icon)).perform(ViewActions.click())
    longOperation(
        longOperation = { Thread.sleep(1000) },
        callback = {onView(withId(R.id.result_list)).check(isVisible())})
}

private fun longOperation(
    longOperation: ()-> Unit,
    callback: ()-> Unit
){
    Thread{
        longOperation()
        callback()
    }.start()
}

0

我将添加此方法的方式添加到混合中:

fun suspendUntilSuccess(actionToSucceed: () -> Unit, iteration : Int = 0) {
    try {
        actionToSucceed.invoke()
    } catch (e: Throwable) {
        Thread.sleep(200)
        val incrementedIteration : Int = iteration + 1
        if (incrementedIteration == 25) {
            fail("Failed after waiting for action to succeed for 5 seconds.")
        }
        suspendUntilSuccess(actionToSucceed, incrementedIteration)
    }
}

这样称呼:

suspendUntilSuccess({
    checkThat.viewIsVisible(R.id.textView)
})

您可以将参数最大迭代次数,迭代长度等添加到suspendUntilSuccess函数。

我仍然更喜欢使用空闲资源,但是例如,由于设备上的动画缓慢而导致测试无法正常进行时,我可以使用此功能,并且效果很好。当然,它可以像失败前一样挂起最多5秒钟,因此,如果成功的操作永远不会成功,则它可能会增加测试的执行时间。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.