带测试的 MVP - 第 1 部分(MVP 架构)

2018-07-23 08:00飞浪

MVP 架构

本教程包含有关 Android 应用程序架构原理的信息以及如何开发应用程序以结合 MVP 架构,从而改进和促进测试过程、代码可重用性、可扩展性以及 UI 组件与应用程序逻辑的分离。

Android 开发中的传统方法遵循MVC架构,如下所示

MVC

  • 模型:存储数据,可能包含逻辑。

  • 视图:视图仅仅是一个代表输出的虚拟占位符。

  • 控制器:充当管理器,向用户提供响应并与用户界面交互。

Activity 或 Fragment 经常充当Controller 的角色,它包含了Sharedpreference的所有实现逻辑,网络回调,以及模型和数据库交互,同时还包括 Activity 生命周期回调。

最终,代码会急剧增长,维护、扩展和重构变得更加困难。MVC 与 Android API 紧密耦合,以至于逻辑实现无法通过单元测试进行测试,或者需要付出大量努力才能创建模拟测试环境。这就引出了下一种架构方法,即清洁架构

清洁架构

为了将逻辑与 Android API 分离,社区采用了干净的代码架构来创建分层架构;将模型、逻辑和用户界面分离在各个层中。

清晰的架构有助于实现代码

  • UI 和逻辑的分离:UI 和逻辑实现是分开维护的,以合并更改而不影响其他层,这意味着演示者永远不必处理与 Android 框架相关的组件和视图。

  • 自适应数据源实现:利用存储库模式,可以在将来的任何时候更改数据源。

  • 可测试性:可以独立于 UI、数据库和任何外部源进行业务逻辑测试。

  • 可重用性:可以通过将现有模块添加到新项目中来重用代码结构。

有关清洁架构的详细解释,请阅读本文。

最有价值球员

为了解决架构问题,MVP 将应用程序分为三层,所有用户事件都委托给 Presenter。

  • 模型:模型负责管理数据,与数据来源无关。模型的职责是缓存数据并验证数据源;无论是网络还是本地数据源,如 sqlite、文件等。模型通常通过存储库模式实现,以抽象出数据源的内部实现。
  • View:View 的唯一任务是显示由 Presenter 指示的内容。View 接口可以由 Activity、Fragment 或其子类型实现。View 保留对 Presenter 的引用,以将用户事件委托给 Presenter 并执行面向 UI 的任务,例如填充列表、显示对话框或更新屏幕上的内容。
  • Presenter:Presenter 是连接 Model 和 View 之间的通信桥梁。根据 View 传递的用户事件,Presenter 查询模型、转换数据,并将更新的数据传递给 View 以显示在屏幕上。

笔记:

  • 在大多数情况下,Presenter 和 View 是一对一绑定的,尽管一个 view 可能有多个 presenter。

  • 接口被定义并实现为View和Presenter之间的通信契约。

  • 视图(又称 被动视图)永远不应该从模型中更新自身。

  • 演示者处理所有业务逻辑、模型更新和视图更新调用。

  • 要执行单元测试,演示者应避免使用任何 Android API。在本地机器上运行 JVM 测试所需的时间较少,因为它不需要任何 UI 交互(Android 框架组件)、模拟器或物理设备。

实现基本 MVP 模式

该项目演示了从网络请求接收并显示电影列表的活动的工作(为了演示而在本地实现)。

这是项目结构的直观表示:

      package com.example.pavneet_singh.mvptestingdemo.mvp.movieslist;

----------------------------------------------------------------

.model                  : POJO and Repository pattern

.mvp.movielist          : MVP implementation for Movie List Screen

.util                   : To imitate a network response and provide helpful classes
    
  1. 实现被动视图:视图应实现为被动视图。MoviesListActivity实现MoviesListContract.View接口,以向演示者等其他组件公开功能。
      // passive view
public class MoviesListActivity extends AppCompatActivity implements MoviesListContract.View{
    

Presenter 将保存对视图的引用并调用由 View 接口公开的方法。从数据源获取数据的过程将由 Presenter 处理。这种被动视图方法借助模拟对象简化了测试阶段。

  1. Android API Free Presenter:Presenter 处理来自 UI 的事件,并使用接口回调机制与长后台线程和进程进行通信。Presenter 实现了MoviesListContract.Presenter
      public final class MoviePresenter implements MoviesListContract.Presenter  {
    

制作可测试的演示者的关键点是避免使用 android API,因为通常常见的 android API(如Sharedpreference、数据库)需要上下文,而这些上下文在没有活动生命周期参与的情况下无法获取,因此要通过 IDE 上的 JVM 进行本地 junit 测试,需要使用mockito框架来创建虚拟对象。

  1. View 和 Presenter 的完整契约:View和Presenter接口被组合成一个单独的接口,称为Contract,该接口将用于在View和Presenter的具体实现之间建立关系。

电影列表合同.java

      public interface MoviesListContract {
   // implemented by MoviesListActivity to provide concrete implementation
   interface View {
       void showProgress(); // display progress bar

       void hideProgress(); // hide progress bar

       void showMovieList(List<Movie> movies); // receive response to display

       void showLoadingError(String errMsg); // display error
   }

   // implemented by MoviesPresenter to handle user event
   interface Presenter{
       void loadMoviewList();

       void dropView();
   }

   // implemented by MoviePresenter to receive response from asynchronous processes
   interface OnResponseCallback{
       void onResponse(List<Movie> movies);

       void onError(String errMsg);
   }
}
    

View接口只是指示 UI 行为,而Presenter的具体实现将通过调用View的方法来管理 UI 的状态。

与面向结果的数据操作相比,Presenter 具有更多针对用户操作的特定方法。Presenter 还使用OnResponseCallback接口与后台任务和进程建立通信链接。

将 Presenter 和 View 保留在单个界面中的原因是

  • View也是 Android 中的一个类。因此,我们可以将界面命名为View,而不会造成任何混淆。

  • 视图和演示器功能均可在一个地方使用,从而提高了清晰度。

  • Presenter 和 View 都具有服务于单个实体(活动或片段)的特定行为,因此将相同的东西放在一起更有意义。

制作演示者时的重要点

  • 不要在 Presenter 中创建生命周期,否则你的 Presenter 将与 Android 组件紧密相连,而 Android 组件之间又存在很大差异。这会使 Presenter 的依赖性更强,并且修改起来非常困难。
  • 在演示者中引入一个接受视图引用的方法需要在很多地方进行空检查,因此如果可以的话请避免这样做而使用构造函数。
  • 使用类似LRUCache的缓存机制来保留数据,保持presenter无状态,并让它在activity或者fragment创建的时候重新创建。
  1. 连接中的 View 和 Presenter:MoviePresenter将通过构造函数接收View的引用,即MoviesListActivity,以及MovieRepo接口的具体实现,该接口将包含从数据源(网络、本地数据库、文件等)检索数据的逻辑。

下面是演示者的具体实现,即MoviePresenter.java:

      public final class MoviePresenter implements MoviesListContract.Presenter  {
  // to keep reference to view
  private MoviesListContract.View view;

  // Repository pattern, mclient holds reference to concrete data retrieval implementation

  private MovieRepo mclient;

  public MoviePresenter(MoviesListContract.View view,MovieRepo client) {
    this.view = view;

    mclient = client;
  }

  @Override
  public void dropView() {
      view = null;
  }

  // would be triggered by MovieListActivity
  @Override
  public void loadMoviewList() {
      view.showProgress();

      mclient.getMovieList(callback);

      // required for espresso UI testing

      // to wait till response occurred

      EspressoTestingIdlingResource.increment();
  }

  // callback mechanism , onResponse will be triggered with response

  // by simulatemovieclient(or your network or database process) and pass the response to view
  private final OnResponseCallback callback = new OnResponseCallback() {
      @Override
      public void onResponse(List<Movie> movies) {

          view.showMovieList(movies);

          view.hideProgress();

          EspressoTestingIdlingResource.decrement();
      }

      @Override
      public void onError(String errMsg) {

          view.hideProgress();

          view.showLoadingError(errMsg);

          EspressoTestingIdlingResource.decrement();
      }
  };
}
    

当用户执行操作(在屏幕上向下滑动)时, MoviesListActivity将触发loadMoviewList方法,然后屏幕上将显示一个进度条,直到收到响应并通过view.showMovieList(movies);方法传递给活动以显示列表。

曝光35880浏览1870