【问题标题】:Using Espresso to test drawable changes使用 Espresso 测试可绘制的更改
【发布时间】:2015-11-17 17:34:09
【问题描述】:

我是 Espresso 测试的新手,但似乎没有任何方法可以测试可绘制的更改。

我有一个教程是一个 ImageView Drawable 幻灯片'塞进'一个半透明的TextView。在我的测试中,我想确保当按下下一个按钮时,正确的Drawable 已经插入到教程的ImageView 中。

没有默认的Matcher 来检查Drawables,所以我开始使用https://stackoverflow.com/a/28785178/981242 编写我自己的。不幸的是,由于无法检索ImageView 的活动Drawable 的ID,我无法完成matchesSafely() 的实现。

这不是测试活动Drawables 的唯一用例。人们通常在这种情况下使用什么工具?

【问题讨论】:

    标签: android android-espresso


    【解决方案1】:

    我不想比较位图,而是遵循这个答案的建议:https://stackoverflow.com/a/14474954/1396068

    在设置图像视图的可绘制对象时,还要将可绘制对象的 ID 存储在其标签中,并使用setTag(R.drawable.your_drawable)。然后使用 Espresso 的 withTagValue(equalTo(R.drawable.your_drawable)) 匹配器来检查标签是否正确。

    【讨论】:

    • 只需添加完整的 withTagValue 代码:withTagValue(CoreMatchers.<Object>equalTo(R.drawable.your_drawable))));。谢谢。 :)
    • 这个解决方案只有一个问题 - 您需要在生产中添加仅用于测试的代码,这是一个很大的禁忌。
    • 并且您可以删除实际背景并保留标签,您的 UI 测试仍然可以工作并且不会反映现实
    • 你能解释一下比较位图的缺点吗?
    • 如果您更改单一颜色、填充或字体,测试将失败,您必须更新位图。这很脆弱,需要做很多工作,而且会经常发生@MohamedKhaled
    【解决方案2】:

    请查看我找到的本教程。 似乎工作得很好https://medium.com/@dbottillo/android-ui-test-espresso-matcher-for-imageview-1a28c832626f#.4snjg8frw

    这是复制意大利面的摘要;-)

    public class DrawableMatcher extends TypeSafeMatcher<View> {
    
        private final int expectedId;
        String resourceName;
    
        public DrawableMatcher(int expectedId) {
            super(View.class);
            this.expectedId = expectedId;
        }
    
        @Override
        protected boolean matchesSafely(View target) {
            if (!(target instanceof ImageView)){
                return false;
            }
            ImageView imageView = (ImageView) target;
            if (expectedId < 0){
                return imageView.getDrawable() == null;
            }
            Resources resources = target.getContext().getResources();
            Drawable expectedDrawable = resources.getDrawable(expectedId);
            resourceName = resources.getResourceEntryName(expectedId);
    
            if (expectedDrawable == null) {
                return false;
            }
    
            Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
            Bitmap otherBitmap = ((BitmapDrawable) expectedDrawable).getBitmap();
            return bitmap.sameAs(otherBitmap);
        }
    
    
        @Override
        public void describeTo(Description description) {
            description.appendText("with drawable from resource id: ");
            description.appendValue(expectedId);
            if (resourceName != null) {
                description.appendText("[");
                description.appendText(resourceName);
                description.appendText("]");
            }
        }
    }
    

    请注意,这仅在您的 DrawableBitmapDrawable 时有效。如果您还有VectorDrawable 或其他Drawable,则必须检查此(imageView.getDrawable() instanceOf XXXDrawable)并从中取出位图。除非你有某种简单的 Drawable,你只有一种颜色,所以你可以比较。

    例如,要获取 VectorDrawable 的位图,您必须将 VectorDrawable 绘制到画布并将其保存到位图(当 VectorDrawable 着色时我遇到了一些麻烦)。 如果你有一个 StateListDrawable 你可以得到所选状态的 Drawable 并重复你的 if instanceOf 级联。 对于其他 Drawable 类型我没有任何经验,对不起!

    【讨论】:

    • 如果您的 Drawable 不是 BitmapDrawable(可能是 GradientDrawableColorDrawableShapeDrawable 以及许多其他人),这将不起作用
    • 比较Vector Drawable,更简单的方法是使用getConstantState()。请参阅stackoverflow.com/questions/9125229/…。我已经在简单的矢量绘图(来自材料设计图标集合)上对其进行了测试,它对它们很有效。
    【解决方案3】:

    有一个要点包含来自Frankie SardowithBackground()withCompoundDrawable()withImageDrawable() 匹配器。所有功劳归于他。

    关于图像ID - 你可以输入R.drawable.image_name,然后drawable的ID将被自动检索。

    【讨论】:

    • 这太完美了!
    【解决方案4】:

    基于@wolle 和@FreewheelNat 的帮助,比较(Vector)Drawable:

    public static Matcher<View> withDrawableId(@DrawableRes final int id) {
        return new DrawableMatcher(id);
    }
    
    
    public static class DrawableMatcher extends TypeSafeMatcher<View> {
    
        private final int expectedId;
        private String resourceName;
    
        public DrawableMatcher(@DrawableRes int expectedId) {
            super(View.class);
            this.expectedId = expectedId;
        }
    
        @Override
        protected boolean matchesSafely(View target) {
            if (!(target instanceof ImageView)) {
                return false;
            }
            ImageView imageView = (ImageView) target;
            if (expectedId < 0) {
                return imageView.getDrawable() == null;
            }
            Resources resources = target.getContext().getResources();
            Drawable expectedDrawable = resources.getDrawable(expectedId);
            resourceName = resources.getResourceEntryName(expectedId);
            if (expectedDrawable != null && expectedDrawable.getConstantState() != null) {
                return expectedDrawable.getConstantState().equals(
                        imageView.getDrawable().getConstantState()
                );
            } else {
                return false;
            }
        }
    
    
        @Override
        public void describeTo(Description description) {
            description.appendText("with drawable from resource id: ");
            description.appendValue(expectedId);
            if (resourceName != null) {
                description.appendText("[");
                description.appendText(resourceName);
                description.appendText("]");
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      这是@drakeet 答案的 Kotlin 版本,稍作修改。

      class DrawableMatcher(private val targetContext: Context,
                        @param:DrawableRes private val expectedId: Int) : TypeSafeMatcher<View>(View::class.java) {
      
      override fun matchesSafely(target: View): Boolean {
          val drawable: Drawable? = when(target) {
              is ActionMenuItemView -> target.itemData.icon
              is ImageView -> target.drawable
              else -> null
          }
          requireNotNull(drawable)
          
          val resources: Resources = target.context.resources
          val expectedDrawable: Drawable? = resources.getDrawable(expectedId, targetContext.theme)
          return expectedDrawable?.constantState?.let { it == drawable.constantState } ?: false
      }
      
      override fun describeTo(description: Description) {
          description.appendText("with drawable from resource id: $expectedId")
          targetContext.resources.getResourceEntryName(expectedId)?.let { description.appendText("[$it]") }
      }
      }
      

      【讨论】:

        【解决方案6】:

        我接受@wolle 的回答是有效的,但我想承认,即使对于Java,它也可能比这更简单。它可以转换为 static function(或 Kotlin 中的 companion),还可以清理一些不推荐使用的代码

        无论如何,Kotlin 的代码压缩且不弃用的解决方案是这样的:

            fun drawableIsCorrect(@DrawableRes drawableResId: Int): Matcher<View> {
                return object : TypeSafeMatcher<View>() {
                    override fun describeTo(description: Description) {
                        description.appendText("with drawable from resource id: ")
                        description.appendValue(drawableResId)
                    }
        
                    override fun matchesSafely(target: View?): Boolean {
                        if (target !is ImageView) {
                            return false
                        }
                        if (drawableResId < 0) {
                            return target.drawable == null
                        }
                        val expectedDrawable = ContextCompat.getDrawable(target.context, drawableResId)
                                ?: return false
        
                        val bitmap = (target.drawable as BitmapDrawable).bitmap
                        val otherBitmap = (expectedDrawable as BitmapDrawable).bitmap
                        return bitmap.sameAs(otherBitmap)
                    }
                }
            }
        

        22 行对 44 行,是吗?

        【讨论】:

          【解决方案7】:

          我已经在这里回答了类似的话题:Get the ID of a drawable in ImageView。 该方法基于在自定义 LayoutInflater 中使用指定资源 id 标记视图。整个过程由一个简单的库TagView 自动化。它对 Espresso 测试特别方便,因为您不需要手动标记项目中的每个视图。实际上,您不需要更改任何内容,除非您在运行时设置了一些可绘制对象。在这种情况下,您需要查看 Tagging in runtime 部分。

          因此,您可以仅通过 id 比较两个可绘制对象:

          onView(withId(R.id.imageview)).check(assertTagKeyValue(
                         ViewTag.IMAGEVIEW_SRC.id, android.R.drawable.ic_media_play));
          

          自定义 Espresso 断言 assertTagKeyValue 可用 here

          【讨论】:

          • 它给你未解决的参考assertTagKeyValue
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-10-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多