新聞中心
假設(shè)你已經(jīng)閱讀過RxJava的相關(guān)內(nèi)容,也已經(jīng)體驗(yàn)過像“RxJava入門之實(shí)例解析”中的那些示例,現(xiàn)在打算在自己的代碼中探索一下響應(yīng)式編程了。但是,現(xiàn)在卻一直困擾著如何測(cè)試那些可能會(huì)在代碼庫(kù)中發(fā)現(xiàn)的新功能呢?下面我們將探索一下如何測(cè)試RxJava代碼。

本文內(nèi)容要點(diǎn):
- RxJava含有內(nèi)建的、測(cè)試友好的解決方案。
- 使用TestSubscriber去驗(yàn)證Observable。
- 使用TestScheduler可實(shí)現(xiàn)對(duì)時(shí)間的嚴(yán)格控制。
- Awaitility庫(kù)提供了對(duì)測(cè)試環(huán)境進(jìn)一步的控制。
使用響應(yīng)式編程,就必須轉(zhuǎn)變對(duì)給定問題的推理方式,因?yàn)槲覀円劢褂谧鳛槭录鞯牧鲃?dòng)數(shù)據(jù),而非個(gè)別數(shù)據(jù)項(xiàng)。事件通常是被不同的線程所產(chǎn)生和消費(fèi),因此在編寫測(cè)試時(shí)必須要對(duì)并發(fā)問題有著清晰的認(rèn)識(shí)。幸運(yùn)的是,RxJava提供了測(cè)試Observable和Subscription的內(nèi)建支持,并且是直接構(gòu)建于RxJava的核心依賴中。
***步
讓我們回顧一下在“RxJava入門之實(shí)例解析”一文中所給出的那個(gè)詞匯的例子,看一下如何對(duì)該例子作測(cè)試。讓我們從基礎(chǔ)測(cè)試工具的設(shè)置開始。在我們的測(cè)試架構(gòu)中,使用了JUnit作為測(cè)試工具。
事實(shí)上在沒有給定調(diào)度器(Scheduler)的情況下,Subscription將默認(rèn)運(yùn)行于調(diào)用線程上。因此我們將在***測(cè)試中使用原生的方法。這意味著我們可實(shí)現(xiàn)一個(gè)Subscription接口的對(duì)象,在Subscription發(fā)生后就立刻對(duì)其狀態(tài)做斷言(assert)。
注意這里使用了顯式的List
TestSubscriber不僅可替代用戶累加器,還另給出了一些行為。例如它能夠給出接收到的消息和每個(gè)事件相關(guān)數(shù)據(jù)的規(guī)模,它也可對(duì)Subscription被完成且在Observable消費(fèi)期間沒有錯(cuò)誤出現(xiàn)的狀態(tài)做斷言。雖然當(dāng)前測(cè)試中的Observable并未生成任何的錯(cuò)誤,但是回到“RxJava入門之實(shí)例解析”一文,我們從中得知了Observable將例外與數(shù)據(jù)事件等同對(duì)待。我們可通過如下的方式通過連接例外事件而模擬錯(cuò)誤:
在我們所給出的有限用例中,所有的機(jī)制運(yùn)行良好。但是實(shí)際的產(chǎn)品代碼可能會(huì)完全不同于例子。因此在下文中,我們將考慮一些更加復(fù)雜的產(chǎn)品實(shí)例。
定制調(diào)度器(Scheduler)
在產(chǎn)品代碼中,很多用例中的Observable都是在特定的線程上執(zhí)行,這種線程在響應(yīng)式編程環(huán)境中被稱為“調(diào)度器(Scheduler)”。很多Observable操作將可選的調(diào)度器參數(shù)作為附加參數(shù)使用。RxJava定義了一系列任何時(shí)候都可用的命名調(diào)度器,包括IO調(diào)度器(io)、計(jì)算調(diào)度器(computation,為共享線程)和新線程調(diào)度器(newThread)。開發(fā)人員也可去實(shí)現(xiàn)個(gè)人定制的調(diào)度器。讓我們通過指定計(jì)算調(diào)度器來(lái)修改Observable的代碼吧。
當(dāng)運(yùn)行時(shí)就會(huì)立刻發(fā)現(xiàn)該代碼是存在問題的。Subscriber在測(cè)試線程上執(zhí)行其斷言,但是Observable在后臺(tái)線程(計(jì)算線程)上生成值。這意味著執(zhí)行Subscriber斷言可能先于Observable生成所有相關(guān)事件,因而導(dǎo)致測(cè)試的失敗。
為使測(cè)試順利執(zhí)行,有如下的一些策略可選:
- 將Observable轉(zhuǎn)化為阻塞式的。
- 強(qiáng)制測(cè)試等待,直至給定的條件被滿足。
- 將計(jì)算調(diào)度器轉(zhuǎn)換為即刻(Schedulers.immediate())調(diào)度器。
我們將對(duì)每個(gè)策略做展開介紹,但將從“將Observable轉(zhuǎn)化為阻塞式”開始,因?yàn)閷?shí)現(xiàn)該策略所需做的技術(shù)工作最少,這些工作與所使用的調(diào)度器無(wú)關(guān)。我們假設(shè)數(shù)據(jù)在后臺(tái)線程中生成,這將導(dǎo)致Subscriber從同一后臺(tái)線程得到通知。
我們要做的是強(qiáng)制生成所有的事件,并在下一個(gè)聲明被執(zhí)行前就在測(cè)試中完成Observable。這是通過在Observable自身上調(diào)用toBlocking()方法實(shí)現(xiàn)的。
該方法雖然適用于我們所給出的簡(jiǎn)單代碼,但可能并不適用于實(shí)際的產(chǎn)品代碼。如果生產(chǎn)者生成所有的數(shù)據(jù)需要很長(zhǎng)的時(shí)間,那將會(huì)產(chǎn)生什么后果?這將使測(cè)試變得非常慢,并增加了編譯時(shí)間,還可能會(huì)有其它的性能問題。這里我推薦一個(gè)便利的程序庫(kù),就是Awaitility(https://github.com/awaitility/awaitility)。簡(jiǎn)單地說,Awaitility是一個(gè)以精確、簡(jiǎn)單易讀的方式對(duì)異步系統(tǒng)相關(guān)期望進(jìn)行表述的DSL。在項(xiàng)目中可以用Maven添加Awaitility的依賴關(guān)系。
org.awaitility awaitility 2.0.0 test
或是使用Gradle:
- testCompile 'org.awaitility:awaitility:2.0.0'
Awaitility DSL的接入點(diǎn)是org.awaitility.Awaitility.await()方法(參見下面例子中的第13和14行代碼)??梢允褂肁waitility定義使測(cè)試?yán)^續(xù)所必須達(dá)成的條件,也可在條件中加入超時(shí)或其它的時(shí)序約束,例如最小、***或持續(xù)范圍。對(duì)于上面的例子,下面的代碼給出了如何在結(jié)果中使用Awaitility:
此版本測(cè)試并未以任何方式改變Observable的本質(zhì),這使得你做測(cè)試時(shí)不必對(duì)產(chǎn)品代碼做任何改動(dòng)。該版本測(cè)試使用最多2秒的等待時(shí)間通過檢查Subscriber狀態(tài)使Observable執(zhí)行其作業(yè)。如果一切進(jìn)行順利,在2秒內(nèi)就可將Subscriber的狀態(tài)釋放給所有的9個(gè)事件。
Awaitility具有和Hamcrest的匹配符、Java 8的lambda表達(dá)式和方法引用等的良好協(xié)作,從而給出精確的和可讀的測(cè)試條件。Awaitility還提供了預(yù)制擴(kuò)展,用于那些被廣泛使用的JVM語(yǔ)言,其中包括Groovy和Scala。
我們要給出***一個(gè)策略中使用了RxJava的擴(kuò)展機(jī)制,該擴(kuò)展是以RxJava API的組成部分發(fā)布的。RxJava中定義了一系列的擴(kuò)展點(diǎn),允許對(duì)幾乎任何默認(rèn)的RxJava行為進(jìn)行微調(diào)。這種擴(kuò)展機(jī)制使我們可以針對(duì)特定的RxJava特性提供修改過的值。利用該機(jī)制,在無(wú)需關(guān)心生成代碼中所指定的調(diào)度器的情況下,我們可在測(cè)試中注入選定的調(diào)度器。這正是我們所尋找的方法,該方法被封裝在RxJavaHooks類中。假設(shè)產(chǎn)品代碼依賴于計(jì)算調(diào)度器,我們將覆蓋它的默認(rèn)值,返回一個(gè)調(diào)度器,它作為被調(diào)用的代碼使事件處理發(fā)生,這是即刻調(diào)度器(Schedulers.immediate())。下面給出測(cè)試的代碼:
在測(cè)試中,產(chǎn)品代碼察覺不到計(jì)算調(diào)度器是即刻的。請(qǐng)注意鉤子函數(shù)必須被重置,否則即刻調(diào)度器的設(shè)置可能會(huì)發(fā)生泄漏,導(dǎo)致在各處的測(cè)試被破壞。使用try/finall代碼塊會(huì)在一定程度上模糊了測(cè)試的目的,但是幸運(yùn)的是我們可以使用JUnit規(guī)則重構(gòu)該行為,使測(cè)試更加精煉,結(jié)果更可讀。下面給出使用上述規(guī)則的一種可能的實(shí)現(xiàn)代碼:
此外,我們還對(duì)另外兩個(gè)調(diào)度器的生成方法做了重寫。該規(guī)則對(duì)此后其它的測(cè)試目標(biāo)更為通用。在新的測(cè)試用例類中,該規(guī)則的使用方法很直接,只需簡(jiǎn)單地定義一個(gè)域,并將其中新類型標(biāo)注為@Rule即可。示例代碼如下:
最終我們可得到與前面測(cè)試一樣的行為,卻沒有像前面測(cè)試那樣的雜亂。下面用一些篇幅來(lái)回顧一下我們目前已經(jīng)做到的事情:
- Subscribers將在同一線程中處理數(shù)據(jù),只要沒有使用特定的調(diào)度器。這意味著在Subscriber向Observable做訂閱后,我們就可在該Subscriber上做斷言。
- TestSubscriber可累計(jì)事件,并給出自身狀態(tài)的追加斷言。
- 任何Observable都可轉(zhuǎn)換為阻塞式的,這使得無(wú)論Observable使用何種調(diào)度器,我們都可以同步等待事件的生成。
- RxJava提供了擴(kuò)展機(jī)制,允許開發(fā)人員重寫其默認(rèn)方法,并以適當(dāng)?shù)姆绞阶⑷氲疆a(chǎn)品代碼中。
- 并發(fā)代碼可使用Awaitility DSL測(cè)試。
上述的每個(gè)技術(shù)都作用于不同的場(chǎng)景中,但是所有技術(shù)都是通過“共同的線程”(譯者注:作者在原文中指出common thread是作為雙關(guān)語(yǔ)使用的,其另一個(gè)意思是“類似的思路”)相關(guān)聯(lián):在對(duì)Subscriber狀態(tài)做斷言之前,測(cè)試代碼需等待Observable完成??紤]到Observable的行為會(huì)生成數(shù)據(jù),是否有方法對(duì)該行為進(jìn)行檢查呢?換句話說,是否可以用編程的方式做Observable的現(xiàn)場(chǎng)調(diào)試?我們將在后文中給出這樣的技術(shù)。
操控時(shí)間
到目前為止我們已用黑箱方式測(cè)試了Observable和Subscription。下面我們將考慮另外一種操控時(shí)間的技術(shù),該技術(shù)使我們可以在Observable依然處于活動(dòng)狀態(tài)時(shí),打開引擎蓋去查看Subscriber狀態(tài)。換句話說,我們將使用采用了RxJava的TestScheduler類白箱測(cè)試技術(shù),這可以說是RxJava再一次來(lái)救場(chǎng)。這種特定的調(diào)度器可精確地設(shè)定時(shí)間的內(nèi)部使用方式,例如可將時(shí)間提前半秒,或是使時(shí)間跳躍5秒。我們將首先給出這種新調(diào)度器實(shí)例的創(chuàng)建方法,然后再討論代碼的測(cè)試。
該“產(chǎn)品”代碼有了略微的改變,這是由于我們使用了綁定到調(diào)度器時(shí)隙(interval())的方法生成計(jì)數(shù)(第6行),而非生成一個(gè)計(jì)數(shù)的范圍。但這樣做具有一個(gè)副作用,就是計(jì)數(shù)是從零開始生成的,而非從1開始。一旦配置了Observable和測(cè)試調(diào)度器,我們立刻做出這樣的斷言,即假定Subscriber不具有值(第15行)且沒有被完成或生成任何的錯(cuò)誤(第16行)。這是一個(gè)完整性測(cè)試,因?yàn)榇藭r(shí)調(diào)度器并沒有被移動(dòng),因而沒有任何值被Observable產(chǎn)生或是被Subscriber接收到。
下面將時(shí)間向前調(diào)1整秒(第19行),該操作將會(huì)導(dǎo)致Observable生成***個(gè)值,這正是隨后的斷言集所要檢查的(第22到24行)。
下面將時(shí)間從當(dāng)前時(shí)間調(diào)到9秒。需要注意的是,這意味著將時(shí)間準(zhǔn)確地調(diào)整為調(diào)度器啟動(dòng)后的第9秒(并非是向前調(diào)1秒后再向前調(diào)9秒,即調(diào)度器檢查啟動(dòng)后的第10秒)。換句話說,advanceTimeBy()方法將調(diào)度器的時(shí)間調(diào)整為相對(duì)于當(dāng)前位置的時(shí)間,而advanceTimeTo()以絕對(duì)的方式調(diào)整時(shí)間。此后我們做出下一輪的斷言(第28到20行),用于確保所有的數(shù)據(jù)由Observable生成且被Subscriber消費(fèi)。另一件需要說明的事情就是使用TestScheduler時(shí),真實(shí)的時(shí)間是立刻發(fā)生調(diào)整的,這著意味著測(cè)試并不用實(shí)際等待9秒才去完成。
正如你所看到的,該調(diào)度器的使用是非常便利的,僅需將該調(diào)度器提供給正在測(cè)試的Observable即可。但是對(duì)使用了指定類型調(diào)度器的Observable,該調(diào)度器并不能很好地適用。但是稍等一下,之前我們看到的是如何使用RxJavaHooks切換一個(gè)不影響生產(chǎn)代碼的調(diào)度器,而這一次是提供一個(gè)代替即刻調(diào)度器的TestScheduler(第13到15行)。我們甚至可以apply定制JUnit規(guī)則同樣的技術(shù),使之前的代碼可以用更重用的方式予以重寫。首先該新規(guī)則為:
緊接著是實(shí)際的測(cè)試代碼(在一個(gè)新的測(cè)試用例類中),去使用我們的測(cè)試規(guī)則:
這樣你就成功地實(shí)現(xiàn)了它。使用經(jīng)由RxJavaHooks注入TestScheduler的方法,可在無(wú)需更改原始Observable組合的情況下編寫測(cè)試代碼,此外它給出了一種在observable自身執(zhí)行期間改變時(shí)間、并在特定點(diǎn)上做斷言的方法。在本文中給出的所有這些技術(shù),應(yīng)該足夠你選擇用來(lái)測(cè)試RxJava的代碼了。
未來(lái)
RxJava是***為Java提供響應(yīng)式編程能力的程序庫(kù)之一。為了使RxJava API更好地符合Reactive Streams規(guī)范,即將推出的2.0版將會(huì)是重新設(shè)計(jì)的。Reactive Streams規(guī)范以Java和JavaScript運(yùn)行時(shí)為目標(biāo),提供了使用非阻塞背壓機(jī)制(back pressure)的異步流處理標(biāo)準(zhǔn)。這意味著下一版的RxJava中將會(huì)出現(xiàn)一些API改進(jìn)。對(duì)這些改進(jìn)的詳細(xì)描述參見RxJava wiki。
對(duì)于測(cè)試而言,這些核心類型(Observable、Maybe和Single)現(xiàn)在都給出了便利易用的test()方法,實(shí)現(xiàn)現(xiàn)場(chǎng)創(chuàng)建TestSubscriber實(shí)例。也可在TestSubscriber上鏈接方法調(diào)用,對(duì)這類用法也有一些新的斷言方法。
原文作者:Andres Almiray
本文名稱:如何測(cè)試RxJava代碼
轉(zhuǎn)載源于:http://m.fisionsoft.com.cn/article/cdcogpd.html


咨詢
建站咨詢
