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

18
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>

非常感谢您的建议。

它是从网络还是资源加载图片? - Eugen Martynov
2个回答

29
我不确定如何对ImageView进行单元测试。
我认为你走错了方向:你想测试Glide是否按预期工作。这不是作为该库的客户端你的责任。Glide有自己的测试来验证它是否按预期工作,你只需对你在应用程序中实现的逻辑进行单元测试。
尽管如此,如果你仍想做类似的事情,那么你必须在ViewHolder中引入一些分离:一个负责将图像加载到ImageView中的组件。
```java public interface ImageLoader {
void load(Context context, String path, @DrawableRes int placeholder, ImageView imageView); } ```
其实现如下:
```java 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将变成这样:
```java 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类。
现在来看看测试。这是设置:
```java @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); } ```
这是测试方法:
```java @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中。

我不是在尝试测试Glide API,而只是想测试图像是否已成功加载到ImageView中,或者只是确保Glide使用了正确的参数进行了调用。

这两个陈述基本上是同一件事情:如果您验证了是否使用正确的参数委托了任务给Glide,那么它就会验证Glide是否会正确地加载图像。

现在,问题归结为如何验证您将任务委派给Glide时是否使用了正确的参数?

在上面提到的场景中:


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

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

只是试图找到确保ImageView具有图像的最佳方法

您想要创建一个抽象层,该抽象层将使用某些模拟的Drawable填充ImageView,并在测试中检查ImageView是否实际上已填充了该drawable。这不是与验证调用您的抽象层的方法(在上面提到的情况下为ImageLoader#load())完全相同吗?因此,没有必要显式检查ImageView是否已使用Drawable填充,因为只要您也模拟了该组件,它肯定会填充。

我想这意味着模仿Glide

不要依赖具体实现,而要依赖抽象。如果您以后决定从Glide移到SomeAwesomeImageLoder,那么您需要更改源码以及测试。另一方面,如果您有一个负责图像加载的类,就可以仅在该类中封装加载逻辑,因此只有该类需要进行更改。此外,这提供了执行单元测试的绝佳接口。


1
好的回答,我只是不喜欢将Glide流畅API更改为具有4-5个参数的接口方法。因此,更好的答案将具有控制接口和模仿Glide API的流畅API。 - Eugen Martynov
@azizbekian,感谢您的出色答案。实际上,也许我应该重新措辞一下我想要实现的内容。我并不是试图测试Glide API,而是试图找到确保图像视图具有图像的最佳方法,或者确保正确调用了Glide(我想这意味着模拟Glide)。希望这样更清楚了。谢谢。 - ant2009
1
@azizbekian。非常详细、非常棒的答案,正是我需要的。 - ant2009

2
在Robolectric中,您可以获取设置为您的imageview的drawable并对其进行断言。
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 中测试并可行。

出现了以下问题: 期望值:<2130837650> 实际值:<android.graphics.drawable.BitmapDrawable@ffffffe0> 看起来我只是在比较实际的可绘制对象而不是资源ID。似乎我正在将一个可绘制对象与整数进行比较。 - ant2009

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接