Android单元测试—MVP中的Presenter测试(1)

发表于:2022-11-15 09:24

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:AxeChen    来源:稀土掘金

  很多人在面试的时候回答MVP的优点会提出:“有利于单元测试”。但是很多程序员没有写单元测试的习惯,特别是小型的创业公司,由于大量的编码工作使程序员将测试的任务全部交给了测试部门。实际上单元测试能够减少逻辑上的错误和bug量。
  1、Presenter中的逻辑测试
   这里只对Presenter的测试进行说明,Presenter的测试相对于Mode和View的测试更加重要。因为主要的逻辑代码写在这里。
  1.1、Presenter测试的逻辑原理
  在测试代码中编写一些测试案例,断言可能出现的结果来测试功能代码的逻辑。在MVP架构中,将View和Model分开了,用Presenter进行View和Model通信。用断言的方式来列举出Model可能出现的数据,验证Presenter中的逻辑判断,然后检查View层执行的方法。 其中:测试的过程控制、测试逻辑、测试案例等用到Junit框架;用于断言和检查方法执行用到Mockito框架。
   1.2、单元测试依赖的测试框架
   Presenter为逻辑测试,主要依赖Junit和mokito框架。
          // 测试相关
      testImplementation "junit:junit:4.12"
      testImplementation "org.mockito:mockito-core:1.10.19"
  在进行单元测试前,必须了解Junit和Mockito提供的注解和方法,否则就无法进行测试。 比如:
以上图片的内容来自文章
  2、在MVP框架中的单元测试实战
  假如现在在做一个登录的功能,登录的需求如下。
  1、登录成功。(当服务返回的code为0。)
  2、登录失败。(当服务其返回的code不为0。)
  3、登录失败,因为用户名为空。
  4、登录失败,因为密码为空。
  2.1、非TTD测试驱动开发模式的单元测试
  (什么是TTD测试驱动开发?后面会提到) 按照需求,会去创建MVP结构来做这个模块。(注意:1、这里展示的是最简单的MVP结构。2、网络请求框架我用的是已经封装好的Rxjava+Retorfit。3、api用的是WanAndroid的api)
  在LoginContract中根据需求定义接口:
      public interface LoginContract {
      public interface View {
          // 登录成功。(当服务返回的code为0。)
          void loginSuccess();
          // 登录失败。(当服务其返回的code不为0。)
          void loginFail();
          // 登录失败,因为用户名错误
          void loginFailCauseByErrorUserName();
          // 登录失败,因为用户名错误
          void loginFailCauseByErrorPassword();
      }
      public interface Model {
          Observable<Response<JSONObject>> login(String userName, String userPwd);
      }
      public interface Presenter {
          void login(String userName, String userPwd);
      }
  }
  Presenter中的代码:
      /**
   * 登录Presenter层
   */
  public class LoginPresenter implements LoginContract.Presenter {
      private LoginContract.Model model;
      private LoginContract.View view;
      private BaseSchedulerProvider schedulerProvider;
      private CompositeDisposable mDisposable;
      public LoginPresenter(LoginContract.Model model,LoginContract.View view){
          this.view = view;
          this.model = model;
          mDisposable = new CompositeDisposable();
          schedulerProvider = SchedulerProvider.getInstance();
      }
      /**
       * 这个是为单元测试建的构造函数,原因是因为Rxjava线程切换,必须设置为立即执行才能测试通过
       * @param model
       * @param view
       * @param schedulerProvider
       */
      public LoginPresenter(LoginContract.Model model,LoginContract.View view,SchedulerTestProvider schedulerProvider){
          this.view = view;
          this.model = model;
          mDisposable = new CompositeDisposable();
          this.schedulerProvider = schedulerProvider;
      }
      @Override
      public void login(String userName, String userPwd) {
          if (TextUtils.isEmpty(userName)) {
              view.loginFailCauseByErrorUserName();
              return;
          }
          if (TextUtils.isEmpty(userPwd)) {
              view.loginFailCauseByErrorPassword();
              return;
          }
          Disposable disposable = model.login(userName, userPwd).
                  compose(ResponseTransformer.handleResult()).
                  compose(schedulerProvider.applySchedulers())
                  .subscribe(jsonObject -> {
                              view.loginSuccess();
                          },
                          throwable -> {
                              view.loginFail();
                          });
          mDisposable.add(disposable);
      }
  }
  这里要进行测试的就是login(String userName, String userPwd)方法中的逻辑。测试的时候会断言服务器返回的数据,会列举一些登录成功和登录失败的测试案例,如果这些测试案例能够正常运行,那么就代表这些代码的逻辑运行正常。
  新建一个测试类去测试Presenter的逻辑。
新建测试类           
  根据需求写出测试案例初始化Presenter使用的必要类
      /**
   * 登录Presenter的测试类
   */
  public class LoginPresenterTest {
      @Mock
      private LoginContract.Model model;
      @Mock
      private LoginContract.View view;
      private LoginPresenter presenter;
      @Mock
      private SchedulerTestProvider schedulerProvider;
      @Before
      public void setUp() {
          MockitoAnnotations.initMocks(this);
          schedulerProvider = new SchedulerTestProvider();
          presenter = new LoginPresenter(model, view,schedulerProvider);
      }
      /**
       * 登陆成功
       * @throws Exception
       */
      @Test
      public void loginSuccess() throws Exception {
      }
      /**
       * 登录失败,服务器返回错误的code
       * @throws Exception
       */
      @Test
      public void loginFailByServer() throws Exception{
      }
      /**
       * 登录失败,错误的用户名
       * @throws Exception
       */
      @Test
      public void loginFailByErrorUserName() throws Exception{
      }
      /**
       * 登录失败,错误的密码
       * @throws Exception
       */
      @Test
      public void loginFailByErrorPwd() throws Exception{
      }
  }
  这里写出登录成功loginsuccess()的测试代码:
          @Test
      public void loginSuccess() throws Exception {
          // 1、断言model.login的方法返回正确的Response数据
          when(model.login("123321","123321")).thenReturn(Observable.just(new Response<>(0,new JSONObject(),"")));
          // 2、Presenter执行登录逻辑
          presenter.login("123321","123321");
          // 3、预测回调给view层的方法是否被调用
          verify(view).loginSuccess();
      }
  这里的测试逻辑为: 
  1、断言model返回正确的数据。 
  2、presenter去调用登录的方法。 
  3、检测view最后会调用的方法。
  然后单独测试这个方法。 
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号