单元测试 Glide:确保 ImageView 有正确的图像

Unit test Glide: make sure ImageView has correct image

Android Studio 3.0 Beta 5
robolectric:3.3.1

我有一个使用 glide 库加载图像 url 的以下视图容器。我正在尝试找到一种方法对此进行单元测试:

    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());
        }
    }

这是我完成的单元测试,但我不确定如何对图像视图进行单元测试。我不确定使用 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 <org.robolectric.shadows.ShadowBitmapDrawable@ffffffe0>
     but: was <android.graphics.drawable.BitmapDrawable@ffffffe0>
Expected :is <org.robolectric.shadows.ShadowBitmapDrawable@ffffffe0>

Actual   :<android.graphics.drawable.BitmapDrawable@ffffffe0>

非常感谢您的任何建议。

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

but I am not sure how can I unit test the image view

我认为你走错了路:你想测试 Glide 是否按预期工作。作为该图书馆的客户,这不是您的责任。 Glide 有自己的测试来验证它是否按预期工作,您应该只对您在应用程序中实现的逻辑进行单元测试。

然而,如果您仍然想做类似的事情,那么您必须在 ViewHolder 中引入一些分离:一个负责将图像加载到 ImageView.[=35= 中的组件]


    public interface ImageLoader {

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

谁的实现在class:

之后

    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 class。

现在开始测试。这是设置:


    @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.

的逻辑

I wasn't trying to test the Glide API, but only to test that the image was successfully loaded into the imageview, or maybe just make sure the glide was called with the correct parameters.

这两个语句基本上是同一件事:如果你验证你将作业委托给 Glide 并使用正确的参数,那么它会验证 Glide 将正确加载图像。

现在,问题归结为如何验证您是否使用正确的参数将作业委托给 Glide?

在上述场景中:


    holder.populateActor(actor);

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

这将检查是否使用这些参数查询了 imageLoader

just trying to find the best way to ensure the image view has an image

你想要的是创建一个抽象,用一些模拟 Drawable 填充 ImageView,然后在你的测试中你会检查 ImageView 是否真的被充满了那个drawable。这不完全一样吗,您验证抽象的方法是否被调用(在上述情况下 ImageLoader#load())?所以,没有必要明确地检查 ImageView 是否被 Drawable 填充,因为它肯定会,只要你也模拟了那个组件。

I guess this would mean mocking the Glide

不依赖实现,依赖抽象。如果您稍后决定从 Glide 移动到 SomeAwesomeImageLoder 怎么办?您必须更改源代码和测试中的所有内容。

另一方面,如果你有一个负责图像加载的 class,你将只在 class 中封装加载逻辑,因此只有这个 class需要改变。此外,这为执行单元测试提供了完美的接缝。