a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 系統 > Android > 正文

深入探討Unit Testing in Android

2020-04-11 12:22:34
字體:
來源:轉載
供稿:網友
1. Testing for ContentProvider
在你開始為Provider寫Case之前,應該仔細讀一讀SDK文檔中關于Provider測試的說明。但是光讀那些說明,你還是沒辦法寫出正確的Case,因為你也知道,Android的文檔是比較差勁的,有一些關鍵東西文檔中沒有說明,你也知道,這在Android當中并不少見。
你寫個Provider的Case,如下:
復制代碼 代碼如下:

public class DemoProviderTest extends ProviderTestCase2<FeedProvider> {
}

編譯有錯誤,它說ProviderTestCase2沒有隱式的構造,看來我們需要一個構造函數,寫一個標準的JUnit構造吧!
復制代碼 代碼如下:

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

WTF,還是有編譯錯誤,而且更嚴重!難道ProviderTestCase2不是繼承自TestCase,用了Eclipse的建議,它創建了一個帶有二個參數的構造:
復制代碼 代碼如下:

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
    }
}

但是僅一個名字的FeedProviderTest(String name)還是有錯誤,再試試不帶參數的,還是不行,這說明ProviderTestCase2沒有這樣的構造函數,但是沒有道理啊,因為它畢竟是繼承自TestCase的啊!很神奇和詭異啊!
既然ProviderTestCase2沒有一個參數的構造,那么只能去掉帶有一參數的構造了!
復制代碼 代碼如下:

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);
    }
}

寫了一個基本的測試,運行了下,得到了一個Warning,是由JUnit Framework報出來的說DemoProviderTest沒有定義公共的構造函數TestCase(name)或TestCase(),什么情況,不是我不定義而是有編譯錯誤啊,因為該死的ProviderTestCase2沒有這二個構造!該死,只能再把這個構造加回來!但是因為父類沒有,只能引用父類的雙參數的構造了!
復制代碼 代碼如下:

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);
    }
}

但是參數傳什么呢?先用Null試試中吧!完全有錯誤,在父類的構造初始化時出現了NPE,這說明傳Null肯定是不對的!看了下強加的帶有二個參數的構造DemoProviderTest(Class<FeedProvider> providerClass, String providerAuthority),也說應該傳一個Class對象,和Provider的Authority,再試試看!
復制代碼 代碼如下:

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了,但是這樣一來二個參數的構造就沒有意義了,于是讓一個參數的調用二個參數的:
復制代碼 代碼如下:

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

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

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

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

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

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

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

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

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);
    }
}

總結一下,從這個例子得到的經驗是,對于組件的測試,都要繼承自android.test.*下面的組件測試框架,但是需要給這些組件測試框架傳遞正確的參數,否則Case無法測試:
二個構造函數
復制代碼 代碼如下:

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

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

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

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里面,由于其系統架構的特性決定了給Android寫單元測試用例和驗證測試用例特別因難
a. Activity reuse
原因就是每一個測試的包,測試的包也是一個Apk,每一個包只能注入一個目標Apk,也就是說只能針對一個Apk里面的內容進行測試,一旦某個操作跳到了Apk以外的地方,就超出了測試框架的控制范圍。但是組件重用機制在Android中非常的普遍,通過Intent來跳到其他的應用(apk)中,調用其他應用的組件來完成某個操作,這是Android的特性,是再普遍不過的了!但這就給單元測試用例埋下了無法逾越的障礙。測試框架本身更弱,一但跳出了某個組件,Instrumentation便無法對其進行控制,開源測試框架robutium-solo一定程度上解決了這個問,Solo可以操作一個包內的任何組件,特別地它能夠解決多個Activity跳轉的問題,但是如前所述,因為一個測試Apk只能注入一個目標Apk,所以一旦Activity跳到了應用外,Solo也沒有了辦法。這是一個無解的問題。因此,Android當中做測試,只能關注一些邏輯層,API層,數據和Provider,Service等一些與表層操作較遠的代碼!對于表層Activity跳來跳去的情況,只能做部分測試,或用MockObject來解決,但是這通常失去了測試的本身意義,因為要花大量時間去創建MockObject,不值!
b. ActionBar is not clickable
還有一非常惡心的問題是,對于Activity的ActionBar無法直接點擊,真的不明白Google到底在搞什么,弄出來個新東東,竟然測試框架里面不支持操作!想到點擊ActionBar只能通過Solo來點擊屏幕坐標,這非常難以移植和維護!
說到操作,還不得不說原生框架Instrumentation支持的操作非常少,而且不好用,它只能派發KeyEvent事件,很多情況下都不好用,比如有個對話框,想要點擊Okay或是Cancel的話,就很麻煩,再如想點擊一個ListView中的某一項的話也是非常麻煩!同樣第三方的robotium-solo框架就好用多了,它進行了很好的封裝,通過Solo.clickOnText()就可以方便的點擊屏幕上的帶有此文字的View。它的內部實現方式是通過View的顯示Tree,根據Tag(文字)來查找相關的View,然后對其發送點擊事件!這也解釋了為什么Solo也無法點擊ActionBar,因為ActionBar不是在Activity的View中,它是像StatusBar一樣,屬于系統級別的東西!
c. StatusBar belongs to Settings.apk
難以想象吧,隨處可見的Statusbar竟然以屬于Settings,只有注入了Settings的包才能對Statusbar進行操作。所以雖然Statusbar上面有你的Apk的相關的東西(比如提示)但是你還是無法直接操作它,除非你寫一個專門注入Settings.apk的測試包!
4. Security Concern
測試的代碼(Instrumentation和TestRunner)也是以一個Apk的形式存在的,它可注入任何目標Apk,然后就可以對其進行操作,甚至獲取其資源和數據。這就帶來了安全上面的問題!可以把一個帶有測試代碼的Apk當成一個應用,一旦在某個手機運行,但可以操作任何一個應用。
其實,這本來不是問題,如果應用市場能對開發者上傳的應用進行嚴格的測試和審核。但是現在的問題是無論是Google Play還是其他市場都不怎么測試,所以就會讓不良者有機可乘!
其實,這里的關鍵問題在于,Android廠商不要盲目的追求數量!把應用集中銷售是Apple想出來的主意,Apple的App Store也是做的最好的!Android只是一個效仿者,所以你發展的慢,數量不多,質量不夠,收入不好,是正常的,因為你是一個追隨者,你起步晚!對于廠商來講,數量你沒有辦法控制,無法一下子弄出幾萬個應用來,這個是需要時間的,但是,至少,你可以嚴格控制質量啊!你可以做到對上傳的應用進行嚴格的測試,這是對用戶負責,也是對自己負責啊!所以無論是設備還是應用程序,都是Apple的要優質一些,Android總是要殘次一些,所以你看Apple的東西價格就高,Android就便宜,當然價格也是Android的唯一優勢!現的社會是一分錢一分貨,便宜自然就沒好貨!
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 久久亚洲精品国产一区最新章节 | 国产在线一区二区三区四区 | 国产高清不卡 | 99精品国产99久久久久久97 | 在线视频中文字幕 | 91在线视频免费观看 | 久草免费在线视频 | 国产精品美女一区二区三区四区 | 精品亚洲一区二区三区四区五区 | 久久久久国 | 色婷婷一区二区 | 欧美3区 | 国产综合精品一区二区三区 | 黑人巨大精品欧美黑白配亚洲 | 亚洲欧美91| 亚洲区视频在线 | 美女操网站 | 成人欧美一区二区三区在线播放 | 午夜三区 | 一区二区不卡在线观看 | 欧美99视频| 国产高清在线精品一区二区三区 | 欧美日韩在线免费观看 | 国产视频1 | 日韩欧美大片在线观看 | 久久蜜桃av一区二区天堂 | 青青草在线免费观看 | 成人毛片在线观看 | 国产中文在线 | 毛片网站在线 | 国产精品96久久久久久久 | 成人精品视频在线 | 中文在线观看www | 亚洲精品无 | 亚洲精品久久久久久久久久久 | 午夜资源 | 精品国产乱码久久久久久1区2区 | 日韩专区一区二区三区 | 欧美日韩精品一区二区三区在线观看 | 久久国产成人 | 91精品国产aⅴ |