自從接觸到單元測試後,每次看UT的文章開頭就一定會有:單元測試的必要性和好處,這裏我就不累述了。
於是我漸漸入坑,加了相關羣,雖然學習起來比較吃力,但是漸漸也感覺到了好處
我想,可能是單元測試讓我更加理解Android的開發原則,對解耦、依賴理解更加深入。寫好一個好的測試,不僅可以檢測BUG,更能讓你的代碼更完美。
最近對RxJava做UI測試的時候,就出現了這樣的情況,點擊按鈕觸發以下事件,用Retrofit+Rxjava進行網絡請求:
api.getResponse(url, param.getParameters())
.map(new Function<HttpResult, T>() {
@SuppressWarnings("unchecked")
@Override
public T apply(HttpResult tHttpResult) throws Exception {
//代碼省略
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
.subscribe(new Observer<T>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(T value) {
//
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
模擬點擊按鈕後RXjava的OnNext事件不觸發,當時我就懵逼了很久,無奈到羣裏發問,雖然沒有得到答案,但是遇到了與我遭遇類似的苦逼騷年。相互討論後,他提到如何讓Rxjava始終處於主線程呢?我隨意的回答:用AndroidSchedulers.mainThread()直接綁定到主線程不就完了。
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread());
咦,果然解決了他的問題,測試通過了。無心插柳柳成蔭,這下終於找到問題所在了,原來是異步請求無法正確執行。
還沒等開心,測試到是通過了,可尼瑪我的按鈕是網絡請求,不會讓我在主線程裏面做耗時操作吧,我同意google也不同意啊。
既然這樣不行,那我就分開進行呀,測試的時候同步,正式運行的時候異步不就好了嗎?哈哈,我TM真是天才。
方案1、BaseSchedulerProvider
說幹就幹,首先我們自定義接口類BaseSchedulerProvider,讓測試和正式代碼個實現一下,這樣來控制Scheduler。
public interface BaseSchedulerProvider {
Scheduler ui();
Scheduler io();
}
====================================
//正式
final class AppSchedulerProvider implements BaseSchedulerProvider {
@Override
public Scheduler ui() {
return AndroidSchedulers.mainThread();
}
@Override
public Scheduler io() {
return Schedulers.io();
}
}
===================================
//測試
public class TestSchedulerProvider implements BaseSchedulerProvider {
@Override
public Scheduler ui() {
return AndroidSchedulers.mainThread();
}
@Override
public Scheduler io() {
return Schedulers.trampoline();
}
}
再在presenter裏面注入一個BaseSchedulerProvider
public LoginPresenter(LoginContract.View mView, UserRepository mUserRepository, mHttpRequest mHttpRequest,
BaseSchedulerProvider mProvider) {
this.mView = mView;
this.mUserRepository = mUserRepository;
this.mHttpRequest = mHttpRequest;
this.mProvider=mProvider;
}
等需要使用的時候就:
.subscribeOn(provider.io())
.observeOn(provider.ui())
然後再在測試的時候:
TestSchedulerProvider mProvider=new TestSchedulerProvider();
mLoginPresenter = new LoginPresenter(mView, mUserRepository, mHttpRequest,mProvider);
這樣,測試的時候自然就會調用測試的TestSchedulerProvider,完美的實現了測試和代碼分離。
接下來,重點來了,看看我的測試代碼:
loginActivity=Robolectric.setupActivity(LoginActivity.class);
Button btn_login = (Button) loginActivity.findViewById(R.id.login_btn_login);
btn_login.performClick();
Intent exceptedIntent=new Intent(loginActivity, homeActivity.class);
ShadowActivity shadowActivity=Shadows.shadowOf(loginActivity);
Intent realIntent=shadowActivity.getNextStartedActivity();
assertEquals(exceptedIntent.getComponent(),realIntent.getComponent());
我才反應過來,我正在UI測試,mLoginPresenter 拿來幹毛啊,一萬隻草泥馬從腦海裏跑過。
不過,用這種方法到是可以很好的解決presenter的單元測試,隨心所欲的轉換線程,我只能這麼安慰自己了。
方案2、RxJavaPlugins
辛苦半天,好像線索又斷了,欲哭無淚啊。毛主席教育我們,打不過就跑!我哪是那種人,哈哈。
當然,又是各種查資料,突然看到了一篇Rxjava1的測試文章:
public static void asyncToSync() {
Func1<Scheduler, Scheduler> schedulerFunc = new Func1<Scheduler, Scheduler>() {
@Override
public Scheduler call(Scheduler scheduler) {
return Schedulers.immediate();
}
};
RxAndroidSchedulersHook rxAndroidSchedulersHook = new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
};
RxJavaHooks.reset();
RxJavaHooks.setOnIOScheduler(schedulerFunc);
RxJavaHooks.setOnComputationScheduler(schedulerFunc);
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(rxAndroidSchedulersHook);
}
原來Rxjava已經準備好了API給我們用,直接利用RxAndroidPlugins、RxJavaHooks這兩個類,就可以控制全局,將IO線程直接轉換爲immediate()線程。RX1的方案這裏不多說,想看詳解的請點這裏看原文
這RX1的方案也不適合我啊,不過既然RX1有,那RX2必然會有,跑去看了官方文檔,千辛萬苦(一個四級水平的孩子)的終於看到了希望的曙光:RxJavaPlugins。
/**
* 單元測試的時候,利用RxJavaPlugins將io線程轉換爲trampoline
* trampoline應該是立即執行的意思(待商榷),替代了Rx1的immediate。
*/
public static void asyncToSync() {
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
然後再在測試類里加這麼一句:
@Before
public void setUp(){
//將rx異步轉同步
RxjavaFactory.asyncToSync();
}
謝天謝地,我終於看到了如下畫面:
真是老淚縱橫啊。
其中的trampoline()線程就是RX1的immediate線程;
Rx2裏面已經找不到RxJavaHooks類了,直接使用RxJavaPlugins即可。
感謝觀看,文中如果有錯誤的地方,大家也不要吝嗇,盡情打臉吧,或者有更好的建議,請在下方留言,相互學習。
要源碼的小夥伴們請點擊:https://github.com/mrqatom/atomRXDT-MVP