本篇介绍如何对MVP架构的项目进行单元测试。会用到之前六篇文章中所介绍的内容,算是学以致用了。本文中我没具体说明的地方前几篇文中一定会有的。希望大家可以循序渐进。

对于MVP网上也有很多变种,各有千秋,但是万变不离其宗。本篇采用的MVP是《Android源码设计模式解析与实战》这本书中介绍的一种,我也有写过相关的读书笔记,没看过这本书的可以简单了解下。我本人还是很喜欢这种MVP的。

1.MVP相关基类

View的接口 : MvpView

public interface MvpView {/**** 获取Context* @return Context*/Context getContext();/**** 显示Progress*/void showProgress();/**** 关闭Progress*/void closeProgress();/**** @param string 消息内容*/void showToast(String string);
}

扮演着view和model的中间层的角色 : BaseMVPPresenter

public abstract class BaseMVPPresenter<T extends MvpView> {/*** View接口类型的弱引用*/private Reference<T> mViewRef;protected T mMvpView;/*** 建立关联*/public void attachView(T view){mViewRef = new WeakReference<>(view);if(isViewAttached()) {mMvpView = getView();}}/*** 获取View* @return View*/public T getView(){return mViewRef.get();}/*** UI展示相关的操作需要判断一下 Activity 是否已经 finish.* <p>* todo : 只有当 isActivityAlive 返回true时才可以执行与Activity相关的操作,* 比如 弹出Dialog、Window、跳转Activity等操作.** @return boolean*/public boolean isViewAttached(){return mViewRef != null && mViewRef.get() != null;}/*** 解除关联*/public void detachView(){if( mViewRef != null){mViewRef.clear();mViewRef = null;}}
}

简单封装的view : BaseMVPActivity

public abstract class BaseMVPActivity<V extends MvpView, T extends BaseMVPPresenter<V>> extends AppCompatActivity implements MvpView{/*** Presenter对象*/protected T mPresenter;public ProgressDialog mProgress;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mPresenter = createPresenter();mPresenter.attachView((V)this);mProgress = new ProgressDialog(this);mProgress.setMessage("加载中...");}@Overrideprotected void onDestroy() {if (mPresenter != null){mPresenter.detachView();}super.onDestroy();}@Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) {super.onRestoreInstanceState(savedInstanceState);if (mPresenter == null){mPresenter = createPresenter();}}/*** 创建Presenter对象* @return Presenter对象*/protected abstract T createPresenter();@Overridepublic Context getContext() {return this;}@Overridepublic void showProgress() {if (mProgress != null && !mProgress.isShowing()){mProgress.show();}}@Overridepublic void closeProgress() {if (mProgress != null && mProgress.isShowing()) {mProgress.dismiss();}}@Overridepublic void showToast(String string) {Toast.makeText(this, string, Toast.LENGTH_SHORT).show();}
}

2.举栗子

这次我们还是采用上一篇的例子。一个简单的登录页面,其中有两个功能:

  • 获取验证码(点击获取验证码后,实现一个120s的倒计时)

  • 登录(验证输入的手机号码与验证码,请求登录接口)

代码很简单,我一一的贴出来:

public interface LoginMvpView extends MvpView{/*** 倒计时完成*/void countdownComplete();/*** 倒计时中* @param time 剩余时间*/void countdownNext(String time);/*** 登录成功*/void loginSuccess();}
public class LoginPresenter extends BaseMVPPresenter<LoginMvpView>{private CompositeDisposable mCompositeDisposable = new CompositeDisposable();public void getIdentify() {// interval隔一秒发一次,到120结束Disposable mDisposable = Observable.interval(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread()).take(120).subscribeWith(new DisposableObserver<Long>() {@Overridepublic void onComplete() {mMvpView.countdownComplete();}@Overridepublic void onError(Throwable e) {mMvpView.showToast("倒计时出现错误!");}@Overridepublic void onNext(Long aLong) {mMvpView.countdownNext(String.valueOf(Math.abs(aLong - 120)));}});mCompositeDisposable.add(mDisposable);}public void login(String mobile, String code) {if(mobile.length() != 11){mMvpView.showToast("手机号码不正确");return;}if(code.length() != 6){mMvpView.showToast("验证码不正确");return;}GithubService.createGithubService().getUser("simplezhli").subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).doOnSubscribe(new Consumer<Disposable>() {@Overridepublic void accept(Disposable disposable) throws Exception {if (isViewAttached()){mMvpView.showProgress();}}}).doAfterTerminate(new Action() {@Overridepublic void run() throws Exception {if (isViewAttached()){mMvpView.closeProgress();}}}).subscribe(new Observer<User>() {@Overridepublic void onSubscribe(Disposable d) {mCompositeDisposable.add(d);}@Overridepublic void onNext(User user) {mMvpView.showToast("登录成功");mMvpView.loginSuccess();}@Overridepublic void onError(Throwable e) {mMvpView.showToast("登录失败");}@Overridepublic void onComplete() {}});}@Overridepublic void detachView(){super.detachView();mCompositeDisposable.clear();}}
public class LoginMVPActivity extends BaseMVPActivity<LoginMvpView, LoginPresenter> implements LoginMvpView, View.OnClickListener{private TextView mTvSendIdentify;private EditText mEtMobile;private EditText mEtIdentify;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);mEtMobile = (EditText) this.findViewById(R.id.et_mobile);mEtIdentify = (EditText) this.findViewById(R.id.et_identify);mTvSendIdentify = (TextView) this.findViewById(R.id.tv_send_identify);this.findViewById(R.id.tv_login).setOnClickListener(this);mTvSendIdentify.setOnClickListener(this);}@Overrideprotected LoginPresenter createPresenter() {return new LoginPresenter();}@Overridepublic void countdownComplete() {mTvSendIdentify.setText(R.string.login_send_identify);mTvSendIdentify.setEnabled(true);}@Overridepublic void countdownNext(String time) {mTvSendIdentify.setText(TextUtils.concat(time, "秒后重试"));}@Overridepublic void loginSuccess() {showToast("登录成功");}@Overridepublic void onClick(View view) {switch (view.getId()){case R.id.tv_send_identify:mTvSendIdentify.setEnabled(false);mPresenter.getIdentify();break;case R.id.tv_login:mPresenter.login(mEtMobile.getText().toString().trim(),mEtIdentify.getText().toString().trim());break;default:break;}}
}

实现代码很简单,我就不具体说明了,主要说说单元测试部分。

3.单元测试

单元测试主要测试两部分:ActivityPresenter

  • Activity部分其实和上一篇大同小异,主要是测试界面上的View的状态变化和文字显示,ToastDialog的弹出与显示内容这些是否符合预期。

  • Presenter 部分测试数据处理的正确性,回调接口的次数与内容是否符合预期。

Activity测试部分代码:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class LoginMVPActivityTest {private LoginMVPActivity loginActivity;private TextView mTvSendIdentify;private TextView mTvLogin;private EditText mEtMobile;private EditText mEtIdentify;@Rulepublic RxJavaTestSchedulerRule rule = new RxJavaTestSchedulerRule();@Beforepublic void setUp(){ShadowLog.stream = System.out;loginActivity = Robolectric.setupActivity(LoginMVPActivity.class);mTvSendIdentify = (TextView) loginActivity.findViewById(R.id.tv_send_identify);mTvLogin = (TextView) loginActivity.findViewById(R.id.tv_login);mEtMobile = (EditText) loginActivity.findViewById(R.id.et_mobile);mEtIdentify = (EditText) loginActivity.findViewById(R.id.et_identify);}@Testpublic void testGetIdentify() throws Exception {Application application = RuntimeEnvironment.application;assertEquals(mTvSendIdentify.getText().toString(),application.getString(R.string.login_send_identify));// 触发按钮点击mTvSendIdentify.performClick();// 时间到10秒rule.getTestScheduler().advanceTimeTo(10, TimeUnit.SECONDS);assertEquals(mTvSendIdentify.isEnabled(), false);assertEquals(mTvSendIdentify.getText().toString(), "111秒后重试");// 时间到120秒rule.getTestScheduler().advanceTimeTo(120, TimeUnit.SECONDS);assertEquals(mTvSendIdentify.getText().toString(),application.getString(R.string.login_send_identify));assertEquals(mTvSendIdentify.isEnabled(), true);}@Testpublic void testLogin() throws Exception {mEtMobile.setText("123");mEtIdentify.setText("123");mTvLogin.performClick();assertEquals("手机号码不正确", ShadowToast.getTextOfLatestToast());mEtMobile.setText("13000000000");mEtIdentify.setText("123");mTvLogin.performClick();assertEquals("验证码不正确", ShadowToast.getTextOfLatestToast());initRxJava();mEtMobile.setText("13000000000");mEtIdentify.setText("123456");mTvLogin.performClick();// 判断ProgressDialog弹出assertNotNull(ShadowProgressDialog.getLatestDialog());assertEquals("登录成功", ShadowToast.getTextOfLatestToast());}private void initRxJava() {RxJavaPlugins.reset();RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {@Overridepublic Scheduler apply(Scheduler scheduler) throws Exception {return Schedulers.trampoline();}});RxAndroidPlugins.reset();RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {@Overridepublic Scheduler apply(Scheduler scheduler) throws Exception {return Schedulers.trampoline();}});}}

Presenter测试部分代码:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class LoginPresenterTest{private LoginPresenter mPresenter;@Mockprivate LoginMvpView mvpView;@Rulepublic MockitoRule mockitoRule = MockitoJUnit.rule();@Rulepublic RxJavaTestSchedulerRule rule = new RxJavaTestSchedulerRule();@Beforepublic void setUp(){//输出日志ShadowLog.stream = System.out;mPresenter = new LoginPresenter();mPresenter.attachView(mvpView);}@Testpublic void testGetIdentify() throws Exception {mPresenter.getIdentify();// 时间到10秒rule.getTestScheduler().advanceTimeTo(10, TimeUnit.SECONDS);// 验证方法被调用10次verify(mvpView, times(10)).countdownNext(anyString());// 时间到120秒rule.getTestScheduler().advanceTimeTo(120, TimeUnit.SECONDS);verify(mvpView, times(120)).countdownNext(anyString());// 验证倒计时完成方法被调用verify(mvpView).countdownComplete();}@Testpublic void testLogin() throws Exception {initRxJava();mPresenter.login("123", "123");verify(mvpView).showToast("手机号码不正确");mPresenter.login("13000000000", "123");verify(mvpView).showToast("验证码不正确");mPresenter.login("13000000000", "123456");verify(mvpView).showProgress();verify(mvpView).loginSuccess();verify(mvpView).closeProgress();}private void initRxJava() {RxJavaPlugins.reset();RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {@Overridepublic Scheduler apply(Scheduler scheduler) throws Exception {return Schedulers.trampoline();}});RxAndroidPlugins.reset();RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {@Overridepublic Scheduler apply(Scheduler scheduler) throws Exception {return Schedulers.trampoline();}});}
}

本篇内容不多,主要是前面讲解内容的一个整合,下一篇会说说MVP结合Dagger的单元测试。所有代码已上传至Github。希望大家多多点赞支持!

Android单元测试(七):MVP与单元测试相关推荐

  1. 解读Android官方MVP项目单元测试

    Google在3月份推出了一个项目,用来介绍Android MVP架构的各种组合,可以认为是官方在这方面的最佳实践.令人称道的是除了MVP本身之外,这些工程配备了极其完善的单元测试用例,学习价值极高. ...

  2. android studio异步单元测试,在Android Studio中可以进行单元测试

    写单元测试类 1.创建单元测试文件夹,即新建一个用于单元测试的包,存放单元测试的类. 2.创建一个类如 ExampleTest,注意要继承自InstrumentationTestCase类. 3.创建 ...

  3. Android当中的MVP模式(七)终篇---关于对MVP模式中代码臃肿

    个人博客:CODE FRAMER BIGZ MVP系列文章配套DEMO Android 当中的 MVP 模式(一)基本概念 Android 当中的 MVP 模式(二)封装 Android 当中的 MV ...

  4. 代码洁癖系列(七):单元测试的地位

    在许多程序员眼中,单元测试似乎是可有可无的,觉得这应该是测试人员的工作.实际上,测试代码和生成代码同样重要.我们不但需要测试代码,而且需要的是整洁的测试代码. 测试为什么要整洁 我们对待测试代码需要像 ...

  5. 浅谈Android中的MVP与动态代理的结合

    浅谈Android中的MVP与动态代理的结合 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 在Android开发平台上接触MVP足足算起来大概已经有一个年头左右.从最开始到现在经 ...

  6. Android上的MVP:如何组织显示层的内容

    MVP(Model View Presenter)模式是著名的MVC(Model View Controller)模式的一个演化版本,目前它在Android应用开发中越来越重要了,大家也都在讨论关于M ...

  7. android mvp模式鸿洋,Android上的MVP模式

    什么是MVP? MVP模式可以分离显示层和逻辑层,所以功能接口如何工作与功能的展示可以实现分离,MVP模式理想化地可以实现同一份逻辑代码搭配不同的显示界面.首先要澄清就是MVP不是一个结构化的模式,它 ...

  8. android物联网开发技术架构,Android 相关七种 CPU 架构适配,android七种

    Android 相关七种 CPU 架构适配,android七种 转载请注明出处:http://blog.csdn.net/kester_/article/details/71055901 NDK 开发 ...

  9. android 适合mvp模式,Android中的MVP:如何使Presenter层系统化?

    MVP(Model View Presenter)模式是著名的 MVC(Model View Controller)的衍生物,并且是 Android 应用程序中管理表示层的***的模式之一. 这篇文章 ...

最新文章

  1. node项目部署到服务器报错,记一次部署node项目到centos服务器经历
  2. oracle json 搜索,oracle 正则查询json返回报文中某个字段的值
  3. 智游推送教你如何使用统计图表辅助运营
  4. 从0开始配置Flutter并运行demo
  5. 可惜Java中没有yield return
  6. Android开发之自带阴影效果的shape
  7. 【转】一键将Web应用发布到云-Azure Web App!
  8. m1 MacBook Air安装原生版本Emacs之方法
  9. Win10的远程桌面
  10. hive中导入csv,本地CSV导入hive表
  11. js中的正则表达式(2)
  12. Linux 开发环境工具 下载网址大全
  13. matlab的三维数组(三维矩阵)
  14. 人工智能与人的职业发展
  15. jtm 一键安装mysql_MySQL数据实时增量同步到Redis
  16. 《码出高效-JAVA开发手册》
  17. 红帽子linux 9.0下载,红帽子 RedHat linux 9.0 简体中文正式版 下载地址
  18. SDIO接口(4)——SDIO通信
  19. 开根号的笔算算法图解_手工开根号原理及其步骤
  20. 汇编语言(十二)颜色搭配显示+BIOS功能调用表+INT 10H功能详细列表

热门文章

  1. php恋爱,ThinkPHP校园恋爱微信表白墙源码
  2. TRECA 崔佧智能低代码开发 软件配置
  3. 一文解读什么是 LeSS(Large Scale Scrum)
  4. 压缩感知基本概括——三大基本问题
  5. 保/防护器件系列之TVS-瞬态抑制二极管
  6. 拼多多新阶段,透露出不寻常
  7. 直流电和交流电的定义
  8. 什么是APS高级计划排程(高级计划排产)?
  9. Mac OS 如何在终端下打开 APP 应用程序?
  10. python panda 库_python基础库-Pandas