Mockito + Robolectrie + RxJava 测试MVP架构项目

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zxm317122667/article/details/79036847

前言

如果你在网上搜 哪种项目架构更好 的时候, 会看到成百上千的博客对各种架构解释优缺点。 但是不幸的是大多数文章都没有提到非常重要的一点: 单元测试
在我们选择某一种项目架构的时候,起决定性因素的无非是个人喜好或者项目需求。我并不认为 MVP 架构比 MVVM 架构更好,或者说 MVP 架构就是一种完美的客户解决方案。让我决定使用 MVP 架构的唯一理由就是它的 简洁性

这里写图片描述

MVP

  1. MVP 代表 Model-View-Presenter
  2. Model 通常理解为data source(数据来源),不管是来自网络或者数据库,甚至是手写的一个List对象也可以被认为是一个数据来源
  3. View 一般可以使用Activity、Fragment或者是一个自定义View来充当。View层的主要功能就是展示界面,拦截用户交互事件
  4. Presenter 在其中扮演着 POJO (plain old java object) 的角色。它负责 View 层和 Model 层的通信。

注意:
在我们去实现某一个 Presenter 的时候,一定要注意将 Model 层可能出现的 Error 信息交给 Presenter 去做统一处理。 并且尽量将业务逻辑从UI层抽离出,放到 Presenter 中去实现

MVP架构测试原则

  1. 首要原则就是要使用JUnit而不是Espresso或者其他的三方自动化测试框架
  2. 其次是对每一层都单独分开测,这一点与集成化测试时截然相反的。因此需要对一些 依赖性注入框架 有一定的了解。我推荐的是 目前火热的 Dagger 框架
  3. 由于我们使用的是JUnit,因此我们需要一个单独测试 UI 功能的框架。 对于此我比较推荐的是目前比较成熟的 Robolectric 框架。
  4. Mockito 框架的使用也是必须的,因为它基本是目前最流行的Mock测试框架了

测试 Model层

Model不能持有 Presenter 和 View 层的引用
Model在Presenter层应该尽量以一个简单的接口的形式存在,尤其是当我们使用三方框架的时候
Model层的测试永远都不应该对项目架构有所依赖,它应该是独立的

案例:

创建Model接口向用户提供相关数据(我们不知道这些数据是来自网络还是数据库), 因为使用RxJava 所以返回类型是Observable

public interface ProfileInteractor {
    Observable<UserProfile> getProfile();
}

而测试这个接口方法的话可以直接使用RxJava提供给我们的 TestSubscriber 类, 具体如下所示:

public class ProfileInteractorTest {
    private static final String USER = "USERNAME";
    ProfileInteractor interactor;

    @Before
    public void setUp() {
        interactor = new ProfileInteractorImpl(...);
    }

    @Test
    public void testGetUserProfile() throws Exception {
        TestSubscriber<UserProfile> subscriber = TestSubscriber.create();
        interactor.getProfile().subscribe(subscriber);
        subscriber.assertNoErrors();
        subscriber.assertCompleted();
        assertThat(subscriber.getOnNextEvents().get(0).getName()).isEqualTo(USER);
    }
}

测试 View 层

对 View 层的测试相对简单一些,难点在于对 Robolectric 的配置。首先还是来看下 View 接口

public interface ProfileView {
    void display(UserProfile userProfile);
}

我们选择的案例是使用一个Android 自定义View来充当 View层

public class ProfileFrameLayout extends FrameLayout implements ProfileView {

    private ProfilePresenter presenter;

    @BindView(R.id.text_username)
    TextView textUsername;

    @Inject
    public void setPresenter(ProfilePresenter presenter) {
        this.presenter = presenter;
        presenter.attachView(this);
    }

    public CollectionFrameLayout(Context context) {
        super(context);
        init();
    }

    public CollectionFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        View view = inflate(getContext(), R.layout.view_profile, this);
        ButterKnife.bind(view);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        presenter.attachView(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        presenter.detachView();
    }

    @Override
    public void display(UserProfile userProfile) {
        textUsername.setText(userProfile.getName());
    }
}

那么问题来了: 对于这个 View 我们应该测试哪些部分或者说是哪些代码应该写测试代码呢?
答案是:EVERYTHING !!所有的都必须测试到位

1 测试 View 是否被成功创建
2 测试默认值是否正确
3 测试用户交互是否正确的传递给了Presenter
4 测试View只是做它应该做的事情(展示界面)
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class ProfileFrameLayoutTest {
    private ProfileFrameLayout profileView;
    @Mock
    ProfilePresenter presenter;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        profileView = new ProfileFrameLayout(RuntimeEnvironment.application);
        profileView.setPresenter(presenter);
    }

    @Test
    public void testEmpty() throws Exception {
        verify(presenter).attachView(profileView);
        asserThat(profileView.textUsername.getText().toString()).isEmpty();
    }

    @Test
    public void testLeaveView() throws Exception {
        profileView.onDetachedFromWindow();
        verify(presenter).detachView();
    }

    @Test
    public void testReturnToView() throws Exception {
        reset(presenter);
        profileView.onAttachedToWindow();
        verify(presenter).attachView(profileView);
    }

    @Test
    public void testDisplay() throws Exception {
        UserProfile user = new UserProfile(USER);
        profileView.display(user);
        asserThat(profileView.textUsername.getText().toString()).isEqualTo(USER);
    }
}

解释:

  1. 最上面的注解是对Robolectrie的配置
  2. 声明一个 ProfileFrameLayout 的全局引用,主要就是对它进行测试
  3. 使用Mockito创建一个mock的Presenter,因为我们只是想通过它来验证 View 层是否会成功调用到 Presenter层的相关代码
  4. 因为android代码中创建View的时候都必须传入一个Context上下文,因此我们使用Robolectric提供给我们的RuntimeEnviroment来作为上下文,并传给ProfileFrameLayout对象,并将Presenter对象传递给它

测试 Presenter 层

测试Presenter和测试Model的方式差不多 只不过这次我们是对View层进行mock。 Presenter将接收一个View对象和一个Model层的接口对象,分别用来展示界面和获取数据

public void ProfilePresenter {
  private final ProfileInteractor interactor;
  private ProfileView view;

  public ProfilePresenter(ProfileInteractor interactor) {
    this.interactor = interactor.
  }

  public void attachView(ProfileView view) {
    this.view = view;
    fetchAndDisplay();
  }

  public void dettachView() {
    // Not covered by this example:
    // You should handle the subscription
  }

  public void fetchAndDisplay() {
    // Not covered by this example:
    // You should handle the subscription
    // You should also check if view is not null
    // You should also handle the onError
    interactor.getUserProfile().subscribe(userProfile -> view.display(userProfile));
  }
}

对于 Presenter 层的测试,我们的主要目标是保证成功从 Model 层拿到数据并成功传递给 View 层展示, 对于数据是否正确我们并不是很在乎。
但是如果你在Presenter层对数据进行了一个转化,那么你就需要对数据的正确性也进行一些相关验证

代码如下:

public void ProfilePresenterTest {
  @Mock
  ProfileInteractor interactor;
  @Mock
  ProfileView view;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    when(interactor.getUserProfile()).thenReturn(Observable.just(new UserProfile()));
    presenter = new ProfilePresenter(interactor);
    presenter.attachView(view);
  }

  @Test
  public void testDisplayCalled() {
    verify(interactor).getUserProfile();
    verify(view).display(any());
  }
}

总结

  1. 创建一个Mockable的 Model, 如果不行就对它进行再抽象再封装
  2. 使用依赖注入框架(Dagger)自动向 View 层注入Presenter对象,不用在View中手动创建Presenter对象
  3. 不要只关注测试输出数据,也要关注对象之间的交互
  4. 如果Presenter对View的声明周期也有依赖,那我们就必须对其进行测试
  5. Test visual changes from your View, not only the text, but also visibility or background color if you change it.对View层的测试要细致到极点,不仅仅是对Text文本的测试,还要对背景颜色,可见性等进行测试
  6. 测试Presenter对Model层提供不同数据时的不同响应

参考链接:https://medium.com/@Miqubel/testing-android-mvp-aa0de6e165e4

猜你喜欢

转载自blog.csdn.net/zxm317122667/article/details/79036847