Robolectric 通過(guò)創(chuàng)建一個(gè)包含真實(shí)Android 框架代碼的運(yùn)行時(shí)環(huán)境來(lái)進(jìn)行工作葫慎。 這意味著泻拦,當(dāng)你的測(cè)試或被測(cè)試代碼調(diào)用到Android框架時(shí)须眷,你會(huì)獲得更真實(shí)的體驗(yàn)黎炉,因?yàn)檫@跟在實(shí)際設(shè)備上執(zhí)行的大部分代碼都是相同的桃煎。然而還是有一些限制:
- Native code(源代碼) - Android源代碼不能在你的開(kāi)發(fā)機(jī)器上執(zhí)行。
2.Out of process calls(進(jìn)程外調(diào)用) - 你的開(kāi)發(fā)機(jī)器上沒(méi)有運(yùn)行Android系統(tǒng)服務(wù)笤受。
3.不足的測(cè)試Api - Android幾乎沒(méi)有適合測(cè)試的api穷缤。
Robolectric通過(guò)一組名字叫做Shadows的類(lèi)來(lái)解決這些問(wèn)題。每一個(gè)shadow 能拓展或繼承Android系統(tǒng)中對(duì)應(yīng)類(lèi)的行為箩兽。當(dāng)一個(gè)Android類(lèi)被實(shí)例化津肛,Robolectric會(huì)去查找一個(gè)對(duì)應(yīng)的shadow類(lèi),如果找到了汗贫,就會(huì)去創(chuàng)建一個(gè)與之關(guān)聯(lián)的shadow對(duì)象身坐。
通過(guò)使用字節(jié)碼工具秸脱,Robolectric能夠編寫(xiě)出跨平臺(tái)偽實(shí)現(xiàn)來(lái)替代原生代碼,并添加額外的api來(lái)實(shí)現(xiàn)測(cè)試部蛇。
名字是什么意思摊唇?
為什么叫“Shadow”?Shadow 對(duì)象們不是完全的Proxies, 不是完全的Fakes涯鲁,不是完全的Mocks或Stubs巷查。Shadows 有時(shí)是隱藏的,有時(shí)是可見(jiàn)的抹腿,并且可以引導(dǎo)你找到真正的對(duì)象岛请。至少我們沒(méi)有叫它們“sheep”,這是我們正在考慮的警绩。
Shadow Classes
Shadow Classes 總是需要一個(gè) public 類(lèi)型的無(wú)參構(gòu)造函數(shù)以便Robolectric框架可以實(shí)例化他們髓需。它們通過(guò)類(lèi)聲明上的@Implements注釋與它們所映射的類(lèi)相關(guān)聯(lián)。
Shadow 類(lèi)應(yīng)該模仿映射類(lèi)的繼承層次關(guān)系房蝉。例如僚匆,如果你實(shí)現(xiàn)一個(gè)ViewGroup的Shadow,ShadowViewGroup,然后你的Shadow類(lèi)需要繼承于ViewGroup的父類(lèi)Shadow,ShadowView搭幻。例如:
@Implements(ViewGroup.class)
public class ShadowViewGroup extends ShadowView{
Methods
Shadow 對(duì)象們實(shí)現(xiàn)了與Android類(lèi)中具有相同特征的方法咧擂。Robolectric將在調(diào)用Android對(duì)象上具有相同特征的方法時(shí)調(diào)用Shadow對(duì)象上的方法。
假設(shè)一個(gè)應(yīng)用程序定義了以下一行代碼:
this.imageView.setImageResource(R.drawable.pivotallabs_logo);
在測(cè)試中檀蹋,Shadow實(shí)例上的shadowimageview# setImageResource(int resId)方法將被調(diào)用松申。
Shadow方法必須用@Implementation注釋標(biāo)記。Robolectric包括一個(gè)校驗(yàn)測(cè)試俯逾,以確保測(cè)試正確進(jìn)行贸桶。
@Implements(ImageView.class)
public class ShadowImageView extends ShadowView{...
@Implementation
protected void setImageResource(intresId){// implementation here.}
}
Robolectric支持在原始類(lèi)上shadowing所有方法,包括private桌肴、static皇筛、final或native。
通常@Implementation方法也應(yīng)該有protected修飾符坠七。 這樣做的目的是減少shadows的API表面范圍;測(cè)試者應(yīng)該總是直接在Android框架類(lèi)上調(diào)用這些方法水醋。
重要的是,shadow方法是在最初定義它們的類(lèi)的相應(yīng)shadow上實(shí)現(xiàn)的彪置。 否則拄踪,Robolectric的查找機(jī)制將找不到它們(即使它們是在shadow子類(lèi)中聲明的)。舉個(gè)例子拳魁,setEnabled()方法定義在View里面惶桐。如果setEnabled()方法被定義在ShadowViewGroup而不是ShadowView,那么即使在一個(gè)實(shí)例化的ViewGroup上調(diào)用setEnabled(),它也不會(huì)在運(yùn)行時(shí)被找到姚糊。
Shadowing Constructors
一旦一個(gè)Shadow對(duì)象被實(shí)例化想虎,Robolectric將尋找一個(gè)名為constructor并帶有@Implementation注解的構(gòu)造方法,該方法與在真實(shí)對(duì)象上調(diào)用的構(gòu)造函數(shù)具有相同的參數(shù)叛拷。
例如,如果應(yīng)用程序代碼調(diào)用TextView構(gòu)數(shù)岂却,它接收一個(gè)Context:
new TextView(context);
Robolectric 將會(huì)調(diào)用如下constructor接收一個(gè)Context的方法:
@Implements(TextView.class)
public class ShadowTextView{
@Implementation
protected void __constructor__(Context context){
this.context=context;
}
Getting access to the real instance(- 訪問(wèn)真實(shí)的實(shí)例)
有時(shí)忿薇,Shadow類(lèi)可能想要引用它們對(duì)應(yīng)類(lèi)的對(duì)象,例如躏哩,操作字段署浩。Shadow類(lèi)可以通過(guò)聲明一個(gè)帶注釋的@RealObject字段來(lái)實(shí)現(xiàn)這一點(diǎn):
@Implements(Point.class)
publicclassShadowPoint{
@RealObject
private Point realPoint;
public void __constructor__(intx,inty){
realPoint.x=x;
realPoint.y=y;
}
}
在調(diào)用任何其他方法之前,Robolectric會(huì)將realPoint設(shè)置為Point的實(shí)際實(shí)例扫尺。
Custom Shadows(自定義)
Robolectric還在改進(jìn)中筋栋,我們依賴(lài)、歡迎并強(qiáng)烈鼓勵(lì)社區(qū)對(duì)bug修復(fù)和功能缺陷的貢獻(xiàn)正驻。然而,如果你想以一種不適合共享的方式修改陰影行為,或者你不能等待一個(gè)新版本包含一個(gè)關(guān)鍵的修復(fù)跛璧,我們支持自定義陰影纬向。
Writing a Custom Shadow(書(shū)寫(xiě)一個(gè)自定義的Shadows)
自定義的shadow結(jié)構(gòu)與普通的shadow類(lèi)非常相似。他們必須在類(lèi)的定義上包含@Implements(AndroidClassName.class)注解伤靠。你可以使用普通shadow的實(shí)現(xiàn)方式捣域,例如通過(guò)@Implementation標(biāo)記實(shí)現(xiàn)方法或者使用public void constructor(...)標(biāo)記構(gòu)造方法。如果你喜歡宴合,你可以讓你的shadow類(lèi)繼承于普通的 Robolectric shadows焕梅。
@Implements(Bitmap.class)
public class MyShadowBitmap {
@RealObject
private Bitmap realBitmap;
private int bitmapQuality = -1;
@Implementation
public boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) {
bitmapQuality = quality;
return realBitmap.compress(format, quality, stream);
}
public int getQuality() {
return bitmapQuality;
}
}
Using a Custom Shadows
使用Shadows數(shù)組屬性,在測(cè)試類(lèi)或測(cè)試方法上使用@Config注釋將自定義shadow連接到Robolectric卦洽。要使用上一節(jié)中提到的MyShadowBitmap類(lèi)贞言,您需要用@Config(shadows={MyShadowBitmap.class})注釋有問(wèn)題的測(cè)試,如果包含多個(gè)自定義shadow:@Config(shadows={MyShadowBitmap.class, MyOtherCustomShadow.class})阀蒂。這使得Robolectric在對(duì)被隱藏的類(lèi)執(zhí)行代碼時(shí)能夠識(shí)別并使用自定義的陰影蜗字。
如果您希望將自定義shadow應(yīng)用于套件或某個(gè)包中的所有測(cè)試,則可以通過(guò)robolectric配置shadow robolectric.properties文件脂新。注意挪捕,默認(rèn)情況下shadows. shadowof()方法不適用于自定義陰影。相反争便,您可以使用Shadow.extract()并將返回值轉(zhuǎn)換為您實(shí)現(xiàn)的自定義Shadow類(lèi)级零。
Building a library of Custom Shadows.
如果您發(fā)現(xiàn)自己正在構(gòu)建一個(gè)自定義shadow庫(kù),那么您應(yīng)該考慮在您的shadow庫(kù)上運(yùn)行Robolectric的shadow注釋處理器。這提供了許多好處奏纪,例如
- 為你的每一個(gè)shadow生成shadowOf方法
- 生成一個(gè)ServiceLoader鉴嗤,這樣如果在類(lèi)路徑中找到自定義shadow,就會(huì)自動(dòng)應(yīng)用它
- 在teardown時(shí)調(diào)用任何static @Resetter方法序调,使您能夠重置靜態(tài)狀態(tài)醉锅。
- 對(duì)shadow執(zhí)行額外的驗(yàn)證和檢查。
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
className 'org.robolectric.annotation.processing.RobolectricProcessor' arguments = [ 'org.robolectric.annotation.processing.shadowPackage' : 'com.example.myshadowpackage' ]
}
}
}
}
dependencies { annotationProcessor project(":processor") }
最佳實(shí)踐:
Limit API surface area of shadows.
因?yàn)镽obolectric 3.7的@Implementation方法发绢,包括constructor方法可以被保護(hù)硬耍。這是可取的,因?yàn)闇y(cè)試代碼沒(méi)有業(yè)務(wù)調(diào)用這些方法边酒,通過(guò)使您的@Implementation方法受保護(hù)经柴,你鼓勵(lì)測(cè)試作者調(diào)用公共Android api代替。
Don’t use useinheritImplementationMethods
這通常是不必要的墩朦,在Robolectric 3.8中將被移除
不要在shadows中重寫(xiě) eaquls, hashCode 和 toString
避免這種情況坯认。要在測(cè)試中測(cè)試相等性以進(jìn)行比較,請(qǐng)選擇輔助程序庫(kù)或斷言庫(kù)氓涣。更喜歡添加一個(gè)describe()方法牛哺,而不是shadowing toString()
編寫(xiě)高質(zhì)量的shadow,以促進(jìn)測(cè)試行為而不是實(shí)現(xiàn)劳吠。
比起使用shadow作為美化的論據(jù)荆隘,更喜歡編寫(xiě)一個(gè)shadow來(lái)鼓勵(lì)測(cè)試行為。例如赴背,不要添加公開(kāi)已注冊(cè)偵聽(tīng)器的方法椰拒,而要為調(diào)用這些偵聽(tīng)器的方法添加@Implementation。
在shadow自己的代碼時(shí)要小心
Robolectric提供了大量的能力凰荚,這需要負(fù)責(zé)任的使用燃观。Shadows是測(cè)試與Android框架交互的理想工具,因?yàn)樵摽蚣懿恢С忠蕾?lài)注入便瑟,并且可以自由使用靜態(tài)代碼缆毁。在為自己的代碼編寫(xiě)自定shadow之前,請(qǐng)考慮是否不能更好地重構(gòu)代碼并使用流行的模仿庫(kù)(如Mockito)到涂。
—————————————————————————————————
個(gè)人總結(jié):
Robolectric通過(guò)Shadow的來(lái)映射Android 中的類(lèi)脊框,這樣就可以直接在單元測(cè)試中使用Android的類(lèi)。當(dāng)Robolectric中提供的Shadow沒(méi)有你需要的類(lèi)的時(shí)候践啄,比如你的自定義View的類(lèi)浇雹,在Robolectric中必然是沒(méi)有的,這個(gè)時(shí)候就可以使用自定義Shadow來(lái)實(shí)現(xiàn)屿讽。