浓咖啡检查是否显示吐司(一个在另一个之上)

时间:2016-07-12 12:37:12

标签: android testing toast android-espresso

我在检查是否使用浓咖啡显示吐司时遇到问题。我正在使用课程:

       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)

有什么奇怪的,当我评论出其中一个测试时,第二个工作正常,没有任何变化。当一个吐司显示在另一个上面时,浓咖啡似乎变得愚蠢。任何想法如何解决这个问题?

2 个答案:

答案 0 :(得分:4)

您看到的NoMatchingViewException表示您的ToastMatcher确实找到了TYPE_TOAST的根视图,但无法在该根目录中找到所请求的视图(否则,您将获得一个NoMatchingRootException)。

我想原因是Android没有在彼此之上显示 ,而是一个接一个。因此,它在吐司根中找到的唯一视图可能是你的第一个吐司(你的第二个吐司还没有显示)。因此,在检查第二次吐司之前,您必须以某种方式等到第一次吐司消失。遗憾的是,这并不是一件轻而易举的事(见下文),我相信你无法改变生产代码。

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

espresso等待事物的方式是IdlingResource s。我目前无法看到如何创建自定义空闲资源来等待吐司而不更改生产代码。因此,即使对生产代码进行必要的更改并不是很有吸引力,但我能够想到的最好的答案是最好的。

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

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()检查。但在那时,吐司将会消失,因此检查失败。我希望这足以说明这种方法的固有问题。来自https://groups.google.com/d/msg/android-test-kit-discuss/uaHdXuVm-Bw/cuQASd3PdpgJ的Valera Zakharov的以下声明似乎证实,使用浓缩咖啡测试吐司没有简单的解决方案:

  

简短回答:不幸的是,在Android中没有线程安全的方法,所以我们不能在Espresso中提供这种功能。
  细节:   实现Toasts的方式使得可以检测已经显示的Toast。然而,无法通过调用show()来查看是否已经请求了Toast,或者在show()和toast可见之间的时间段之间阻止了Toast。这会打开无法解决的时间问题(你只能通过睡眠和希望来解决)[...]。

Zakharov然后还建议在生产代码中添加一些钩子(据我所知)。因此,我想基于一些生产代码钩子添加IdlingResource实际上是你能做的最好的(这也可能使你的吐司测试一般更稳定,因为你可以按照Zakharov的概述测试你的祝酒词)。

答案 1 :(得分:0)

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

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

@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会出错。