Подтвердить что ты не робот

Unit test Glide: убедитесь, что ImageView имеет правильное изображение

Android Studio 3.0 Beta 5
robolectric:3.3.1

У меня есть следующий держатель вида, который загружает URL-адрес изображения, используя библиотеку glide. Я пытаюсь найти способ unit test this:

    public class MovieActorsViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.civActorPicture) CircleImageView actorPicture;
        @BindView(R.id.tvName) TextView name;
        @BindView(R.id.tvCharacter) TextView character;

        private Context context;

        public MovieActorsViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);

            context = itemView.getContext();
        }

        public void populateActor(Actor actor) {
            Glide.with(context)
                    .load(actor.getPicturePath())
                    .placeholder(R.drawable.people_placeholder)
                    .into(actorPicture);

            name.setText(actor.getName());
            character.setText(actor.getCharacter());
        }
    }

Это unit test, я сделал, но я не уверен, как я могу unit test видеть изображение. Я не уверен, что использование Mockito для издевки библиотеки Glide будет работать?

@RunWith(RobolectricTestRunner.class)
public class MovieActorsViewHolderTest {
    private MovieActorsViewHolder movieActorsViewHolder;

    @Before
    public void setup() {
        final Context context = ShadowApplication.getInstance().getApplicationContext();
        final View view = LayoutInflater.from(context).inflate(R.layout.movie_actors_item, new LinearLayout(context));

        movieActorsViewHolder = new MovieActorsViewHolder(view);
    }

    @Test
    public void testShouldPopulateActorWithValidData() {
        final Actor actor = getActor();
        movieActorsViewHolder.populateActor(actor);

        /* test that the image view */
    final ShadowDrawable shadowDrawable = Shadows.shadowOf(movieActorsViewHolder.actorPicture.getDrawable());
    final Drawable drawable = Drawable.createFromPath(actor.getPicturePath());
    assertThat(drawable, is(shadowDrawable.getCreatedFromResId()));

        assertThat(movieActorsViewHolder.name.getText(), is(actor.getName()));
        assertThat(movieActorsViewHolder.character.getText(), is(actor.getCharacter()));
    }

  private Actor getActor() {
    return new Actor(
            "https://image.tmdb.org/t/p/w92/dRLSoufWtc16F5fliK4ECIVs56p.jpg",
            "Robert Danny Junior",
            "Iron Man");
}

}

Вывод:

Expected: is <[email protected]>
     but: was <[email protected]>
Expected :is <[email protected]>

Actual   :<[email protected]>

Большое спасибо за любые предложения.

4b9b3361

Ответ 1

но я не уверен, как я могу unit test видеть изображение

Я думаю, вы ошиблись: вы хотите проверить, работает ли Glide, как ожидалось. Это не ваша ответственность как клиент этой библиотеки. У Glide есть свои собственные тесты, которые подтверждают, что он работает так, как ожидалось, вы должны unit test логики, которые вы реализуете в своем приложении.

Тем не менее, если вы все еще хотите сделать что-то подобное, вам нужно ввести некоторое разделение в свой ViewHolder: компонент, который отвечает за загрузку изображения в ImageView.


    public interface ImageLoader {

        void load(Context context,
                  String path,
                  @DrawableRes int placeholder,
                  ImageView imageView);
    }

В чью реализацию входит следующий класс:


    public class ImageLoaderImpl implements ImageLoader {

        @Override
        public void load(Context context, String path, int placeholder, ImageView imageView) {
            Glide.with(context)
                    .load(path)
                    .placeholder(placeholder)
                    .into(imageView);
        }

    }

Теперь ваш ViewHolder станет примерно таким:


    class MovieActorsViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.picture)
        ImageView imageView;
        // other views

        ImageLoader imageLoader;

        MovieActorsViewHolder(View itemView, ImageLoader imageLoader) {
            super(itemView);
            ButterKnife.bind(this, itemView);

            this.imageLoader = imageLoader;
        }

        void populateActor(Actor actor) {
            imageLoader.load(itemView.getContext(),
                    actor.getPicturePath(),
                    R.drawable.people_placeholder,
                    imageView);

            // other actions                
        }

    }

Это даст вам гибкость, чтобы издеваться над классом ImageLoader.

Теперь к тесту. Здесь настройка:


    @Before
    public void setup() {
        imageLoader = Mockito.mock(ImageLoader.class);

        activity = Robolectric.setupActivity(MainActivity.class);
        ViewGroup root = (ViewGroup) activity.findViewById(R.id.root);

        View inflated = activity.getLayoutInflater().inflate(R.layout.item, root);
        holder = new MovieActorsViewHolder(inflated, imageLoader);
    }

И вот тестовый метод:


    @Test
    public void test() throws InterruptedException {
        final String path = "https://image.tmdb.org/t/p/w92/dRLSoufWtc16F5fliK4ECIVs56p.jpg";
        final Actor actor = new Actor(path);
        final Bitmap bitmap = Shadow.newInstanceOf(Bitmap.class);
        final BitmapDrawable drawable = new BitmapDrawable(activity.getResources(), bitmap);

        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                holder.imageView.setImageDrawable(drawable);
                return null;
            }
        }).when(imageLoader).load(activity, path, R.drawable.people_placeholder, holder.imageView);

        holder.populateActor(actor);

        assertEquals(holder.imageView.getDrawable(), drawable);
    }

Это пройдет. Но спросите себя: что вы испытали с этим? Вместо этого лучшим испытанием было бы удостовериться, что imageLoader.load(...) был вызван с правильными параметрами, игнорируйте логики того, как Glide загрузит это изображение в ImageView.


Я не пытался протестировать API Glide, но только для проверки того, что изображение было успешно загружено в изображение, или просто просто убедитесь, что глиссада вызывается с правильными параметрами.

Эти два утверждения в основном одно и то же: если вы подтвердите, что делегируете задание Glide с правильными параметрами, он проверяет правильность загрузки изображения Glide.

Теперь вопрос заключается в том, как проверить, что вы делегируете задание Glide с правильными параметрами?

В приведенном выше сценарии:


    holder.populateActor(actor);

    verify(imageLoader).load(activity, path, R.drawable.people_placeholder, holder.imageView);

Это проверит, запрашивался ли ImageLoader с этими параметрами.

просто пытается найти лучший способ обеспечить изображение с изображением

Вы хотите создать абстракцию, которая заполнила бы ImageView каким-либо макетом Drawable, и в рамках вашего теста вы проверили бы, действительно ли ImageView был заполнен этой возможностью. Разве это не совсем то же самое, что вы подтверждаете, что был вызван метод вашей абстракции (в вышеупомянутом случае ImageLoader#load())? Таким образом, нет необходимости явно проверять, был ли ImageView заполнен с помощью Drawable, потому что он обязательно будет, если вы тоже издевались над этим компонентом.

Я предполагаю, что это будет означать насмешку над Glide

Не зависит от реализации, зависит от абстракции. Что делать, если позже вы решите перейти от Glide в SomeAwesomeImageLoder? Вы должны изменить все как в своих источниках, так и в тестах.

С другой стороны, если у вас есть класс, отвечающий за загрузку изображений, вы должны инкапсулировать логику загрузки только в этом классе, поэтому только этот класс нуждается в изменении. Кроме того, это дает идеальный шов для проведения модульных испытаний.

Ответ 2

В Robolectric вы можете установить drawable в свой imageview и поместить на него утверждение.

ShadowDrawable shadowDrawable = Shadows.shadowOf(imageView.getDrawable());
        assertEquals(expected, shadowDrawable.getCreatedFromResId());

В вашем случае вы можете сделать -

final Actor actor = getActor();
movieActorsViewHolder.populateActor(actor);
ShadowDrawable shadowDrawable = Shadows.shadowOf(movieActorsViewHolder.actorPicture.getDrawable());
Drawable expected = Drawable.createFromPath(actor.getPicturePath());
assertEquals(expected, shadowDrawable.getCreatedFromResId());

Примечание. Это проверено и работает в Robolectric 3.3.1