欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入探討Unit Testing in Android

 更新時(shí)間:2013年05月22日 10:01:56   作者:  
本篇文章是對(duì)Unit Testing in Android進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
1. Testing for ContentProvider
在你開始為Provider寫Case之前,應(yīng)該仔細(xì)讀一讀SDK文檔中關(guān)于Provider測(cè)試的說明。但是光讀那些說明,你還是沒辦法寫出正確的Case,因?yàn)槟阋仓溃珹ndroid的文檔是比較差勁的,有一些關(guān)鍵東西文檔中沒有說明,你也知道,這在Android當(dāng)中并不少見。
你寫個(gè)Provider的Case,如下:
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
}

編譯有錯(cuò)誤,它說ProviderTestCase2沒有隱式的構(gòu)造,看來我們需要一個(gè)構(gòu)造函數(shù),寫一個(gè)標(biāo)準(zhǔn)的JUnit構(gòu)造吧!
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
    public FeedProviderTest(String name) {
        super(name);
    }
}

WTF,還是有編譯錯(cuò)誤,而且更嚴(yán)重!難道ProviderTestCase2不是繼承自TestCase,用了Eclipse的建議,它創(chuàng)建了一個(gè)帶有二個(gè)參數(shù)的構(gòu)造:
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
    public FeedProviderTest(String name) {
        super(name);
    }

    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
        // TODO Auto-generated constructor stub
    }
}

但是僅一個(gè)名字的FeedProviderTest(String name)還是有錯(cuò)誤,再試試不帶參數(shù)的,還是不行,這說明ProviderTestCase2沒有這樣的構(gòu)造函數(shù),但是沒有道理啊,因?yàn)樗吘故抢^承自TestCase的??!很神奇和詭異啊!
既然ProviderTestCase2沒有一個(gè)參數(shù)的構(gòu)造,那么只能去掉帶有一參數(shù)的構(gòu)造了!
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }

    public void testConstructor() throws Throwable {
        assertNotNull("can construct resolver", getMockContentResolver());
        ContentProvider provider = getProvider();
        assertNotNull("can instantiate provider", provider);
    }
}

寫了一個(gè)基本的測(cè)試,運(yùn)行了下,得到了一個(gè)Warning,是由JUnit Framework報(bào)出來的說DemoProviderTest沒有定義公共的構(gòu)造函數(shù)TestCase(name)或TestCase(),什么情況,不是我不定義而是有編譯錯(cuò)誤啊,因?yàn)樵撍赖腜roviderTestCase2沒有這二個(gè)構(gòu)造!該死,只能再把這個(gè)構(gòu)造加回來!但是因?yàn)楦割悰]有,只能引用父類的雙參數(shù)的構(gòu)造了!
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> { 
    public DemoProviderTest() {
        super(null, null);
    }

    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }

    public void testConstructor() throws Throwable {
        assertNotNull("can construct resolver", getMockContentResolver());
        ContentProvider provider = getProvider();
        assertNotNull("can instantiate provider", provider);
    }
}

但是參數(shù)傳什么呢?先用Null試試中吧!完全有錯(cuò)誤,在父類的構(gòu)造初始化時(shí)出現(xiàn)了NPE,這說明傳Null肯定是不對(duì)的!看了下強(qiáng)加的帶有二個(gè)參數(shù)的構(gòu)造DemoProviderTest(Class<FeedProvider> providerClass, String providerAuthority),也說應(yīng)該傳一個(gè)Class對(duì)象,和Provider的Authority,再試試看!
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
    public DemoProviderTest() {
        super(FeedProvider.class, AUTHORITY);
    }

    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }

    public void testConstructor() throws Throwable {
        assertNotNull("can construct resolver", getMockContentResolver());
        ContentProvider provider = getProvider();
        assertNotNull("can instantiate provider", provider);
    }
}

這次Okay了,但是這樣一來二個(gè)參數(shù)的構(gòu)造就沒有意義了,于是讓一個(gè)參數(shù)的調(diào)用二個(gè)參數(shù)的:
復(fù)制代碼 代碼如下:

    public DemoProviderTest() {
        this(FeedProvider.class, AUTHORITY);
    }

還是Okay,這說明我們的Case必須給ProviderTestCase2提供正確的構(gòu)造參數(shù)!
再加上setUp和tearDown:
復(fù)制代碼 代碼如下:

    @Override
    public void setUp() throws Exception {
        mContentResolver = getMockContentResolver();
    }

    @Override
    public void tearDown() throws Exception {
        mContentResolver = null;
    }

運(yùn)行,發(fā)現(xiàn)testConstructor掛了,說getMockContentResolver()返回的是Null,這怎么可能啊,太詭異了!想到還是可能初始化未正確,給setUp加上了父類的調(diào)用:
復(fù)制代碼 代碼如下:

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mContentResolver = getMockContentResolver();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        mContentResolver = null;
    }

這下再跑,全都Okay了,說明凡是涉及到重寫(Override)父類的方法,都要調(diào)用父類的方法,以期正確初始化!下面是正確的完整版:
復(fù)制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
    private ContentResolver mContentResolver;

    public DemoProviderTest() {
        this(FeedProvider.class, AUTHORITY);
    }

    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mContentResolver = getMockContentResolver();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        mContentResolver = null;
    }

    public void testConstructor() throws Throwable {
        assertNotNull("can construct resolver", getMockContentResolver());
        ContentProvider provider = getProvider();
        assertNotNull("can instantiate provider", provider);
    }
}

總結(jié)一下,從這個(gè)例子得到的經(jīng)驗(yàn)是,對(duì)于組件的測(cè)試,都要繼承自android.test.*下面的組件測(cè)試框架,但是需要給這些組件測(cè)試框架傳遞正確的參數(shù),否則Case無法測(cè)試:
二個(gè)構(gòu)造函數(shù)
復(fù)制代碼 代碼如下:

    public DemoProviderTest() {
        this(FeedProvider.class, AUTHORITY);
    }

    public DemoProviderTest(Class<FeedProvider> providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
    }

一個(gè)都不能少,而且是JUnit的指定構(gòu)造函數(shù)(帶有一個(gè)String,或不帶參數(shù)的)要調(diào)用測(cè)試架構(gòu)指定的構(gòu)造,以給測(cè)試框架傳遞正確的參數(shù)!
還有就是重寫的父類方法時(shí),一定要把父類的方法也調(diào)用上,否則還是不會(huì)初始化正確!
但是這里不得不說這些組件測(cè)試框架寫的真是不好用,首先,那個(gè)名字就讓人費(fèi)解,為什么有個(gè)2?。ndroid真夠2的!還有,既然作為框架,應(yīng)該把初始化的工作做完整,做徹底,這樣才能稱的上框架。使用者應(yīng)該只需要繼承,把自己的事情做完,就應(yīng)該能進(jìn)行工作,就像組件Activity或ContentProvider一樣,到了你的代碼里的時(shí)候,框架里的初始化工作已經(jīng)做完,所以你,繼承者只需要關(guān)心你自已的初始化工作就好!但是測(cè)試框架就爛,繼承者不但要關(guān)心自己的初始化還要保證給父類傳遞正確的參數(shù)!
2. Testing for Activity
同樣對(duì)于Activity的測(cè)試也是要注意初始化的部分,只不過對(duì)于setUp和tearDown你不調(diào)super也沒有關(guān)系!
復(fù)制代碼 代碼如下:

public class ExplorerActivityTester extends
        ActivityInstrumentationTestCase2<ExplorerActivity> {
    public ExplorerActivityTester() {
        this(TARGET_PACKAGE_NAME, ExplorerActivity.class);
    }

    public ExplorerActivityTester(String pkg, Class<ExplorerActivity> class1) {
        super(pkg, class1);
    }

    @Override
    public void setUp() {
        mInstrumentation = getInstrumentation();
    }
}

3. Obstacles to unit testing
在Android里面,由于其系統(tǒng)架構(gòu)的特性決定了給Android寫單元測(cè)試用例和驗(yàn)證測(cè)試用例特別因難
a. Activity reuse
原因就是每一個(gè)測(cè)試的包,測(cè)試的包也是一個(gè)Apk,每一個(gè)包只能注入一個(gè)目標(biāo)Apk,也就是說只能針對(duì)一個(gè)Apk里面的內(nèi)容進(jìn)行測(cè)試,一旦某個(gè)操作跳到了Apk以外的地方,就超出了測(cè)試框架的控制范圍。但是組件重用機(jī)制在Android中非常的普遍,通過Intent來跳到其他的應(yīng)用(apk)中,調(diào)用其他應(yīng)用的組件來完成某個(gè)操作,這是Android的特性,是再普遍不過的了!但這就給單元測(cè)試用例埋下了無法逾越的障礙。測(cè)試框架本身更弱,一但跳出了某個(gè)組件,Instrumentation便無法對(duì)其進(jìn)行控制,開源測(cè)試框架robutium-solo一定程度上解決了這個(gè)問,Solo可以操作一個(gè)包內(nèi)的任何組件,特別地它能夠解決多個(gè)Activity跳轉(zhuǎn)的問題,但是如前所述,因?yàn)橐粋€(gè)測(cè)試Apk只能注入一個(gè)目標(biāo)Apk,所以一旦Activity跳到了應(yīng)用外,Solo也沒有了辦法。這是一個(gè)無解的問題。因此,Android當(dāng)中做測(cè)試,只能關(guān)注一些邏輯層,API層,數(shù)據(jù)和Provider,Service等一些與表層操作較遠(yuǎn)的代碼!對(duì)于表層Activity跳來跳去的情況,只能做部分測(cè)試,或用MockObject來解決,但是這通常失去了測(cè)試的本身意義,因?yàn)橐ù罅繒r(shí)間去創(chuàng)建MockObject,不值!
b. ActionBar is not clickable
還有一非常惡心的問題是,對(duì)于Activity的ActionBar無法直接點(diǎn)擊,真的不明白Google到底在搞什么,弄出來個(gè)新東東,竟然測(cè)試框架里面不支持操作!想到點(diǎn)擊ActionBar只能通過Solo來點(diǎn)擊屏幕坐標(biāo),這非常難以移植和維護(hù)!
說到操作,還不得不說原生框架Instrumentation支持的操作非常少,而且不好用,它只能派發(fā)KeyEvent事件,很多情況下都不好用,比如有個(gè)對(duì)話框,想要點(diǎn)擊Okay或是Cancel的話,就很麻煩,再如想點(diǎn)擊一個(gè)ListView中的某一項(xiàng)的話也是非常麻煩!同樣第三方的robotium-solo框架就好用多了,它進(jìn)行了很好的封裝,通過Solo.clickOnText()就可以方便的點(diǎn)擊屏幕上的帶有此文字的View。它的內(nèi)部實(shí)現(xiàn)方式是通過View的顯示Tree,根據(jù)Tag(文字)來查找相關(guān)的View,然后對(duì)其發(fā)送點(diǎn)擊事件!這也解釋了為什么Solo也無法點(diǎn)擊ActionBar,因?yàn)锳ctionBar不是在Activity的View中,它是像StatusBar一樣,屬于系統(tǒng)級(jí)別的東西!
c. StatusBar belongs to Settings.apk
難以想象吧,隨處可見的Statusbar竟然以屬于Settings,只有注入了Settings的包才能對(duì)Statusbar進(jìn)行操作。所以雖然Statusbar上面有你的Apk的相關(guān)的東西(比如提示)但是你還是無法直接操作它,除非你寫一個(gè)專門注入Settings.apk的測(cè)試包!
4. Security Concern
測(cè)試的代碼(Instrumentation和TestRunner)也是以一個(gè)Apk的形式存在的,它可注入任何目標(biāo)Apk,然后就可以對(duì)其進(jìn)行操作,甚至獲取其資源和數(shù)據(jù)。這就帶來了安全上面的問題!可以把一個(gè)帶有測(cè)試代碼的Apk當(dāng)成一個(gè)應(yīng)用,一旦在某個(gè)手機(jī)運(yùn)行,但可以操作任何一個(gè)應(yīng)用。
其實(shí),這本來不是問題,如果應(yīng)用市場(chǎng)能對(duì)開發(fā)者上傳的應(yīng)用進(jìn)行嚴(yán)格的測(cè)試和審核。但是現(xiàn)在的問題是無論是Google Play還是其他市場(chǎng)都不怎么測(cè)試,所以就會(huì)讓不良者有機(jī)可乘!
其實(shí),這里的關(guān)鍵問題在于,Android廠商不要盲目的追求數(shù)量!把應(yīng)用集中銷售是Apple想出來的主意,Apple的App Store也是做的最好的!Android只是一個(gè)效仿者,所以你發(fā)展的慢,數(shù)量不多,質(zhì)量不夠,收入不好,是正常的,因?yàn)槟闶且粋€(gè)追隨者,你起步晚!對(duì)于廠商來講,數(shù)量你沒有辦法控制,無法一下子弄出幾萬個(gè)應(yīng)用來,這個(gè)是需要時(shí)間的,但是,至少,你可以嚴(yán)格控制質(zhì)量啊!你可以做到對(duì)上傳的應(yīng)用進(jìn)行嚴(yán)格的測(cè)試,這是對(duì)用戶負(fù)責(zé),也是對(duì)自己負(fù)責(zé)啊!所以無論是設(shè)備還是應(yīng)用程序,都是Apple的要優(yōu)質(zhì)一些,Android總是要?dú)埓我恍?,所以你看Apple的東西價(jià)格就高,Android就便宜,當(dāng)然價(jià)格也是Android的唯一優(yōu)勢(shì)!現(xiàn)的社會(huì)是一分錢一分貨,便宜自然就沒好貨!

相關(guān)文章

最新評(píng)論