Использование Espresso для тестирования рисуемых изменений

Я новичок в тестировании эспрессо, но, похоже, нет никакого способа протестировать изменения, которые можно нарисовать.

У меня есть учебник, который представляет собой ImageView Drawable слайд-шоу, «спрятанное» в полупрозрачный TextView. В своих тестах я хочу убедиться, что при нажатии следующей кнопки правильный Drawable был вставлен в ImageView учебника.

По умолчанию нет Matcher для проверки Drawables, поэтому я решил написать свой собственный, используя https://stackoverflow.com/a/28785178/981242. К сожалению, поскольку невозможно получить идентификатор активного Drawable ImageView, я не могу завершить реализацию matchesSafely().

Это не может быть единственным вариантом использования для тестирования активных Drawable. Какой инструмент люди обычно используют в подобных ситуациях?


person nukeforum    schedule 17.11.2015    source источник


Ответы (7)


Я предпочитаю не сравнивать растровые изображения и вместо этого следовать совету этого ответа: https://stackoverflow.com/a/14474954/1396068

При настройке отображаемого изображения изображения также сохраните идентификатор рисования в своем теге с помощью setTag(R.drawable.your_drawable). Затем используйте сопоставители withTagValue(equalTo(R.drawable.your_drawable)) Espresso, чтобы проверить правильный тег.

person Fabian Streitel    schedule 14.06.2017
comment
Просто чтобы добавить полный код withTagValue: withTagValue(CoreMatchers.<Object>equalTo(R.drawable.your_drawable))));. Спасибо. :) - person Francislainy Campos; 12.11.2018
comment
Есть только одна проблема с этим решением — вам нужно добавить код в производство, который будет использоваться только в тестах, и это большое нет-нет. - person Chapz; 26.07.2019
comment
и вы можете удалить фактический фон и сохранить тег, ваш тест пользовательского интерфейса все равно будет работать и не будет отражать реальность - person JPhi Denis; 08.04.2021

пожалуйста, проверьте этот учебник, который я нашел. Кажется, работает довольно хорошо 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("]");
        }
    }
}

Имейте в виду, что это работает только тогда, когда ваш Drawable является BitmapDrawable. Если у вас также есть VectorDrawable или другой Drawable, вы должны проверить это (imageView.getDrawable() instanceOf XXXDrawable) и получить из него растровое изображение. За исключением того, что у вас есть какой-то простой Drawable, где у вас есть только один цвет или так, что вы можете сравнить.

Например, чтобы получить растровое изображение VectorDrawable, вам нужно нарисовать VectorDrawable на холсте и сохранить его в растровое изображение (у меня были некоторые проблемы, когда VectorDrawable был окрашен). Если у вас есть StateListDrawable, вы можете получить Drawable выбранного состояния и повторить каскад if instanceOf. Для других типов Drawable у меня нет опыта, извините!

person wolle    schedule 05.02.2016
comment
Это не сработает, если ваш Drawable не BitmapDrawable, который может быть GradientDrawable, ColorDrawable или ShapeDrawable и многими другими. - person riwnodennyk; 15.06.2016
comment
Для сравнения Vector Drawable проще использовать getConstantState(). См. stackoverflow.com/questions/9125229/. Я протестировал его на простых векторных рисунках (из коллекции значков дизайна материалов), и он хорошо с ними работает. - person FreewheelNat; 06.01.2017

Есть один gist, который содержит withBackground(), withCompoundDrawable(), withImageDrawable() совпадения от Frankie Sardo. Все заслуги перед ним.

Что касается идентификаторов изображений - вы можете ввести R.drawable.image_name, тогда идентификатор чертежа будет получен автоматически.

person denys    schedule 07.02.2016
comment
Это потрясающе! - person danwilkie; 06.09.2018

На основе помощи @wolle и @FreewheelNat для сравнения (вектор) 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("]");
        }
    }
}
person drakeet    schedule 03.06.2017

Вот версия ответа @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]") }
}
}
person John A Qualls    schedule 08.09.2020

Я принимаю ответ @wolle как действительный, но я хотел бы признать, что даже для Java это может быть даже проще. Его можно преобразовать в static function (или companion в Kotlin), а также очистить некоторый устаревший код.

В любом случае, решение для 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, а?

person Rafael Ruiz Muñoz    schedule 01.02.2018

Я уже отвечал на аналогичную тему здесь: Получить идентификатор объекта в ImageView. Этот подход основан на пометке представления указанным идентификатором ресурса в пользовательском файле LayoutInflater. Весь процесс автоматизирован простой библиотекой TagView. Это особенно удобно для теста Espresso, потому что вам не нужно вручную помечать каждое представление в вашем проекте. На самом деле вам не нужно ничего менять, кроме того, что вы устанавливаете некоторые чертежи во время выполнения. В этом случае вам нужно заглянуть в раздел Тегирование во время выполнения.

В результате вы можете сравнить два чертежа только по их идентификаторам:

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

Пользовательское утверждение эспрессо assertTagKeyValue доступно здесь

person Bogdan Kornev    schedule 21.03.2018
comment
Это дает вам неразрешенную ссылку assertTagKeyValue - person Anton Makov; 23.10.2019