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 所以返回类型是Observablepublic 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
相关文章推荐
- MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读
- [置顶] MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读
- 主流的项目架构(MVP+RxJava+Retrofit,热修复等
- Android 开源项目Kotlin+MVP+Retofit2+RxJava2架构开发一款短视频App
- spring Boot测试的最佳实践和测试架构的启发(JUnit4和mockito,包括MockMvc)
- 基于JUnit使用PowerMock的Mockito扩展在Maven测试项目中的配置说明
- MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读
- MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读
- 基于TestNG使用PowerMock的Mockito扩展在Maven测试项目中的配置说明
- Android--带你一点点封装项目 MVP+BaseActivity+Retrofit+Dagger+RxJava(一)
- 基于Android真实项目教你一步一步搭建架构2 -- Google官方Mvp架构
- Spring Test, JUnit, Mockito, Hamcrest 集成 Web 测试
- 如何用PowerMockito 测试静态方法
- RHCA架构调优,安全部分项目设计测试案例
- 通过对MVP架构的学习,继续对练习的项目做总结。
- 强大的Mockito测试框架
- Android MVP架构(RxJava+SQLBrite+Retrofit+OkHttp+Glide)
- MVP架构之后对RxJava的遐想
- 学习项目: mvp+Rxjava+Retrofit
- Android-多列表的项目(Rxjava+Rtrofit+Recyclerview+Glide+Adapter封装)之(一)项目架构