日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android单元测试(七):MVP与单元测试

發布時間:2024/3/24 Android 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android单元测试(七):MVP与单元测试 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本篇介紹如何對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.單元測試

單元測試主要測試兩部分:Activity與Presenter。

  • Activity部分其實和上一篇大同小異,主要是測試界面上的View的狀態變化和文字顯示,Toast, Dialog的彈出與顯示內容這些是否符合預期。

  • 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与单元测试的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。