【问题标题】:Delay test in Espresso Android without freezing main thread在 Espresso Android 中延迟测试而不冻结主线程
【发布时间】:2018-10-15 14:03:19
【问题描述】:

我是 Espresso 测试框架的新手。现在我有一个任务来测试一些适用于异步后端的应用程序。当第一个活动开始时,一些片段仅在加载后出现。这可能需要几秒钟,所以最简单的方法就是等待 5-7 秒。但是使用 IdlingResource 会冻结主线程,所以我的后端数据在等待超时结束之前无法加载。

这就是我使用 IdlingResource 的方式:

public static class ElapsedTimeIdlingResource implements IdlingResource {
    private final long startTime;
    private final long waitingTime;
    private ResourceCallback resourceCallback;

    ElapsedTimeIdlingResource(long waitingTime) {
        this.startTime = System.currentTimeMillis();
        this.waitingTime = waitingTime;
    }

    @Override
    public String getName() {
        return ElapsedTimeIdlingResource.class.getName() + ":" + waitingTime;
    }

    @Override
    public boolean isIdleNow() {
        long elapsed = System.currentTimeMillis() - startTime;
        boolean idle = (elapsed >= waitingTime);
        if (idle) resourceCallback.onTransitionToIdle();
        return idle;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
}

我怎么称呼它:

    long waitingTime = 5000;

    onView(withId(R.id.row_content)).check(matches(isDisplayed())).perform(click());

    IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
    IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

    IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
    IdlingRegistry.getInstance().register(idlingResource);

   // .... do some tests

   IdlingRegistry.getInstance().unregister(idlingResource);

如何在不阻塞主线程的情况下延迟测试执行?

【问题讨论】:

  • 您确定 Espresso 没有因为其他原因而冻结吗?例如,如果您忘记禁用设备/模拟器上的动画,就会发生这种情况。附带说明一下,硬编码等待似乎是一个非常脆弱的解决方案。为什么不让isIdleNow检查数据是否真的已经加载?
  • 是的,动画被禁用。我知道等待一定时间的解决方案并不完美,但我不知道如何以另一种方式做到这一点。

标签: java android testing android-espresso qa


【解决方案1】:

所以我有一个闪屏片段,它在延迟后与我正在测试的片段进行交易。 @Aarons 的答案很有效。

onView(isRoot()).perform(waitFor(5000))

Kotlin 等待():

fun waitFor(delay: Long): ViewAction? {
    return object : ViewAction {
        override fun getConstraints(): Matcher<View> = isRoot()
        override fun getDescription(): String = "wait for $delay milliseconds"
        override fun perform(uiController: UiController, v: View?) {
            uiController.loopMainThreadForAtLeast(delay)
        }
    }
}

【讨论】:

    【解决方案2】:

    如果您只想等待一段时间,则实际上并不需要 IdlingResource:

    public static ViewAction waitFor(long delay) {
        return new ViewAction() {
            @Override public Matcher<View> getConstraints() {
                return ViewMatchers.isRoot();
            }
    
            @Override public String getDescription() {
                return "wait for " + delay + "milliseconds";
            }
    
            @Override public void perform(UiController uiController, View view) {
                uiController.loopMainThreadForAtLeast(delay);
            }
        };
    }
    

    并使用它:

    onView(withId(R.id.row_content)).check(matches(isDisplayed())).perform(click());   
    onView(isRoot()).perform(waitFor(5000);
    

    但是如果您知道视图会在一段时间后出现,那么您可以使用 IdlingResource 例如:

    public static ViewAction waitUntil(Matcher<View> matcher) {
        return actionWithAssertions(new ViewAction() {
            @Override public Matcher<View> getConstraints() {
                return ViewMatchers.isAssignableFrom(View.class);
            }
    
            @Override public String getDescription() {
                StringDescription description = new StringDescription();
                matcher.describeTo(description);
                return String.format("wait until: %s", description);
            }
    
            @Override public void perform(UiController uiController, View view) {
                if (!matcher.matches(view)) {
                    LayoutChangeCallback callback = new LayoutChangeCallback(matcher);
                    try {
                        IdlingRegistry.getInstance().register(callback);
                        view.addOnLayoutChangeListener(callback);
                        uiController.loopMainThreadUntilIdle();
                    } finally {
                        view.removeOnLayoutChangeListener(callback);
                        IdlingRegistry.getInstance().unregister(callback);
                    }
                }
            }
        });
    }
    
    private static class LayoutChangeCallback implements IdlingResource, View.OnLayoutChangeListener {
    
        private Matcher<View> matcher;
        private IdlingResource.ResourceCallback callback;
        private boolean matched = false;
    
        LayoutChangeCallback(Matcher<View> matcher) {
            this.matcher = matcher;
        }
    
        @Override public String getName() {
            return "Layout change callback";
        }
    
        @Override public boolean isIdleNow() {
            return matched;
        }
    
        @Override public void registerIdleTransitionCallback(ResourceCallback callback) {
            this.callback = callback;
        }
    
        @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            matched = matcher.matches(v);
            callback.onTransitionToIdle();
        }
    }
    

    并以它为例:

    onView(withId(R.id.row_content)).check(matches(isDisplayed())).perform(click());
    onView(withId(R.id.main_content)).perform(waitUntil(isDisplayed()))
    

    【讨论】:

    • java.lang.RuntimeException:未找到活动。您是否通过调用 getActivity() 或 startActivitySync 或类似方法来启动活动?在 android.support.test.espresso.base.RootViewPicker.waitForAtLeastOneActivityToBeResumed(RootViewPicker.java:169)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-11
    • 1970-01-01
    • 2014-05-30
    • 1970-01-01
    • 2015-06-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多