【问题标题】:Espresso checking if toasts are displayed (one on top of another)浓缩咖啡检查是否显示吐司(一个在另一个之上)
【发布时间】:2016-07-12 12:37:11
【问题描述】:

我在检查是否使用 espresso 显示 toast 时遇到问题。我正在使用类:

       import android.os.IBinder;
    import android.support.test.espresso.Root;
    import android.view.WindowManager;
    import org.hamcrest.Description;
    import org.hamcrest.TypeSafeMatcher;

    public class ToastMatcher extends TypeSafeMatcher<Root> {

    @Override
    public void describeTo(Description description) {
        description.appendText("is toast");
    }

    @Override
    public boolean matchesSafely(Root root) {
        int type = root.getWindowLayoutParams().get().type;
        if ((type == WindowManager.LayoutParams.TYPE_TOAST)) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            if (windowToken == appToken) {
                // windowToken == appToken means this window isn't contained by any other windows.
                // if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
                return true;
            }
        }
        return false;
    }

}

并通过以下方式检查 Toast:

onView(withText(R.string.unauthorized)).inRoot(new ToastMatcher())
            .check(matches(isDisplayed()));

一切正常,直到我尝试检查同一类中的另一个吐司,例如:

@Test
public void messageOnBack() throws Exception{
pressBack();
onView(withText(R.string.exit_on_back)).inRoot(new ToastMatcher())
            .check(matches(isDisplayed()));

然后第一个通过但第二个出错:

    android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with string from resource id: <2131165323>[unauthorized] value: Wrong login or password.

View Hierarchy:
+>LinearLayout{id=-1, visibility=VISIBLE, width=660, height=116, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}
|
+->AppCompatTextView{id=16908299, res-name=message, visibility=VISIBLE, width=528, height=58, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=66.0, y=29.0, text=Please click BACK again to exit., input-type=0, ime-target=false, has-links=false}
|
at dalvik.system.VMStack.getThreadStackTrace(Native Method)

奇怪的是,当我注释掉其中一个测试时,第二个测试工作正常,没有任何更改。当一个吐司放在另一个吐司上时,浓缩咖啡似乎变得愚蠢。任何想法如何解决这个问题?

【问题讨论】:

  • 编辑:我已经设法通过在正在测试的活动中公开敬酒并在测试外观后取消()每个敬酒来做到这一点,但它似乎不是最佳答案。

标签: android testing toast android-espresso


【解决方案1】:

问题在于,在您测试第二个 toast 时,之前显示的 toast 正在显示。您可以通过使 mToast 成员变量对测试可见来解决此问题,并使用它来取消 @After 中的任何活动 toast,如下所示:

显示 toast 时(被测 Activity 的生产代码):

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Toast mToast;

private void showToast(final String text) {
    mToast = Toast.makeText(this, text, Toast.LENGTH_LONG);
    mToast.show();
}

测试代码(与被测代码在同一个包中):

    @After
    public void tearDown() {
        // Remove any toast message that is still shown:
        Toast toast = mActivityRule.getActivity().mToast;
        if (toast != null) {
            toast.cancel();
        }
    }

要求您稍微更改生产代码,但如果您在其他地方使用成员变量,则在最新版本的 Android Studio 中使用 @VisibleForTesting 会出错。

【讨论】:

  • 谢谢!这是唯一可行的解​​决方案
【解决方案2】:

您看到的NoMatchingViewException 表示您的ToastMatcher 确实找到了带有TYPE_TOAST 的根视图,但在该根中找不到请求的视图(否则,您将获得NoMatchingRootException)。

我猜原因是 Android 没有显示祝酒词一个接一个,而是一个接一个。因此,它在 toast-root 中找到的唯一视图可能是您的第一个 toast(您的第二个 toast 尚未显示)。因此,在检查第二个 toast 之前,您必须以某种方式等到第一个 toast 消失。不幸的是(见下文),这并非微不足道,而且我相信您无法绕过更改生产代码。

https://stackoverflow.com/a/32023568/1059766 中给出了一个可能的解决方案。基本思想是将OnAttachStateChangedListener 附加到toast 的视图之前 显示toast,并使用该侦听器跟踪视图何时附加到视图层次结构和从视图层次结构分离。然后可以使用它来实现一个自定义的IdlingResource,它可以等待 toast 消失。

espresso 等待事物的方式是通过IdlingResources。我目前看不到如何在不更改生产代码的情况下创建自定义空闲资源来等待敬酒。因此,即使对生产代码的所需更改不是很吸引人,我也能想到与上述答案类似的东西。

话虽如此,请注意您的 ToastMatcher 解决方案(通常在 stackoverflow 和博客上建议)也不是测试 toast 的真正可靠方法。它适用于大多数情况,但并非总是如此。考虑例如以下sn-p:

new AsyncTask<Void, Void, Void>() {
    public void doInBackground(...) {
        // start background work for 10s (or just Thread.sleep(10000))
    }
}.execute()
Toast.make(context, R.string.mytoast, Toast.LENGTH_SHORT).show()

由于 espresso 总是等待 UI 线程和所有异步任务空闲,因此在上面的示例中它将等待(大约)10 秒,直到执行 isDisplayed() 检查。但此时 toast 将消失,因此检查失败。我希望这足以说明这种方法的固有问题。 Valera Zakharov 在https://groups.google.com/d/msg/android-test-kit-discuss/uaHdXuVm-Bw/cuQASd3PdpgJ 中的以下声明似乎证实了用浓缩咖啡测试吐司没有简单的解决方案:

简短回答:不幸的是,在 Android 中没有线程安全的方式来执行此操作,因此我们在 Espresso 中不提供此功能。
细节: Toast 的实现方式可以检测到已显示的 toast。但是,无法通过调用 show()) 或在 show() 之间的时间段和 toast 变得可见之间阻塞来查看是否请求了 Toast。这会引发无法解决的时间问题(您只能通过睡眠和希望来解决)[...]。

Zakharov 然后还建议在生产代码中添加一些钩子(据我所知)。因此,我想基于一些生产代码钩子添加 IdlingResource 确实是您能做的最好的事情(这也可能使您的 toast 测试总体上更稳定,因为您可以按照 Zakharov 的概述测试您的 toast)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-23
    相关资源
    最近更新 更多