您的位置:首页 > 运维架构 > 网站架构

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

2018-01-12 11:51 477 查看

前言

如果你在网上搜
哪种项目架构更好
的时候, 会看到成百上千的博客对各种架构解释优缺点。 但是不幸的是大多数文章都没有提到非常重要的一点: 单元测试

在我们选择某一种项目架构的时候,起决定性因素的无非是个人喜好或者项目需求。我并不认为
MVP
架构比
MVVM
架构更好,或者说
MVP
架构就是一种完美的客户解决方案。让我决定使用
MVP
架构的唯一理由就是它的 简洁性



MVP

MVP
代表 Model-View-Presenter

Model
通常理解为data source(数据来源),不管是来自网络或者数据库,甚至是手写的一个List对象也可以被认为是一个数据来源

View
一般可以使用Activity、Fragment或者是一个自定义View来充当。View层的主要功能就是展示界面,拦截用户交互事件

Presenter
在其中扮演着 POJO (plain old java object) 的角色。它负责
View
层和
Model
层的通信。

注意:

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

MVP架构测试原则

首要原则就是要使用JUnit而不是Espresso或者其他的三方自动化测试框架

其次是对每一层都单独分开测,这一点与集成化测试时截然相反的。因此需要对一些 依赖性注入框架 有一定的了解。我推荐的是 目前火热的
Dagger
框架

由于我们使用的是JUnit,因此我们需要一个单独测试 UI 功能的框架。 对于此我比较推荐的是目前比较成熟的 Robolectric 框架。

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


解释:

最上面的注解是对Robolectrie的配置

声明一个 ProfileFrameLayout 的全局引用,主要就是对它进行测试

使用Mockito创建一个mock的Presenter,因为我们只是想通过它来验证 View 层是否会成功调用到 Presenter层的相关代码

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


总结

创建一个Mockable的
Model
, 如果不行就对它进行再抽象再封装

使用依赖注入框架(Dagger)自动向 View 层注入Presenter对象,不用在View中手动创建Presenter对象

不要只关注测试输出数据,也要关注对象之间的交互

如果Presenter对View的声明周期也有依赖,那我们就必须对其进行测试

Test visual changes from your View, not only the text, but also visibility or background color if you change it.对View层的测试要细致到极点,不仅仅是对Text文本的测试,还要对背景颜色,可见性等进行测试

测试Presenter对Model层提供不同数据时的不同响应

参考链接:https://medium.com/@Miqubel/testing-android-mvp-aa0de6e165e4
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Mockito RxJava Roboletric