Build Instrumented Test

Setups

Instrumented Test是基于JUnit的,使用JUnit4的Test Class風格我們可以可以很快的寫出Instrumented Test Class。當然在此之前,我們還需要做一些環境的配置工作。包括:

  • 配置 instrumentation runner。這個runner負責執行Instrumented Test Class!
    android {
          defaultConfig {
              ...
              testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            }
      }
    
  • 配置需要的dependencies,這里將需要的依賴都列出來了,包括基本依賴、UI測試的依賴和其他庫。
    dependencies {
      //這個庫是為了避免因為主程序依賴了相同庫的不同版本導致沖突加入的
      androidTestCompile 'com.android.support:support-annotations:24.0.0'
      //必需的依賴;
      androidTestCompile 'com.android.support.test:runner:0.5'
      //可選依賴,包含Android提供的幾個Rule實現,如ActivityTestRule,ServiceTestRule
      androidTestCompile 'com.android.support.test:rules:0.5'
      // 可選-- Hamcrest library,matcher庫,可以很方便的構建matcher
      androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
      // 可選-- UI testing with Espresso UI測試時使用
      androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
      // 可選 -- UI testing with UI Automator 跨App UI測試時使用
      androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
    

}


##Test UI
主要是使用Espresso庫來進行UI測試。Espresso能夠自動test action和target app的UI狀態,它會在UI線程Idle的時候去執行test code,避免開發者自己去做同步的工作,提高了測試的可靠性。在進行UI測試時,可能需要在開發者選項中將窗口動畫縮放、過渡動畫縮放和動畫時長調整選項關閉,否則可能會導致不可預料的結果或者直接導致測試失敗。Espresso的使用主要分為幾個步驟:
1. 使用`onView()`或者`onData()`方法(針對AdapterView)找到對應的view。`onView()`方法接收一個`Matcher<View>`對象作為參數用來在當前的視圖層次結構中找到對應的view,找不到或者有多個View滿足條件都會拋出異常。可以查看 [ViewMatchers](https://developer.android.com/reference/android/support/test/espresso/matcher/ViewMatchers.html) 看看Espresso提供的matchers。`onData()`主要適用于AdapterView,比如ListView、GridView、Spinner。這些控件可能包含很多的item view,使用`onView()`方法去找,不一定能找到,因為item view可能不在當前視圖層次結構中顯示。所以espresso提供`onData()`方法使用。該方法接收一個`Matcher<Object>`類型的參數用來匹配對應的Item。其實就是在對應的Adapter上調用`getItem()`方法,用返回的Object對象去匹配那個Matcher對象。找到這個Item,espresso會自動滾動到該item的位置。
```java
onView(withText("Sign-in"));
onView(withId(R.id.button_signin));
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));

onData(allOf(is(instanceOf(Map.class)),
      hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str))));
  1. 通過上面兩個方法的返回對View進行操作。onView()方法會返回一個ViewInteraction對象,而onData()方法返回一個DataInteraction對象。兩個對象都有一個perform()方法,該方法接收變長的ViewAction對象作為參數,可以連續執行一些的操作,諸如點擊、滑動、輸入等操作。具體可執行的操作由具體的view決定,查看 ViewActions看看可使用什么操作。
  2. ViewInteraction或者DataInteraction上調用check()方法驗證View的狀態;該方法接收一個ViewAssertion作為參數,查看 ViewAssertions提供了哪些方法幫助構建ViewAssertion
onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
        .perform(click())               // click() is a ViewAction
        .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion

Intents Stub

需要添加espresso-intents依賴庫。

@Rule //需要聲明使用這個Rule,繼承于ActivityTestRule,但會在前后做一些Intents Stub的準備和清理工作
public IntentsTestRule<DialerActivity> mActivityRule = new IntentsTestRule<>(DialerActivity.class);
//如果有匹配matcher的Intent請求,則用result響應
//在啟動外部某個app,希望得到響應時很有用
intending(Matcher<Intent> matcher).respondWith(ActivityResult result);

//驗證當前的正在測試的application發送了一個matcher中指定的Intent
//重載的方法允許驗證匹配的Intent發送了多次
intended(Matcher<Intent> matcher)
intended(Matcher<Intent> matcher, times(2));

Test Service

Service的測試需要用到ServiceTestRule,這個rule幫助我們start或者成功bind到service,在結束測試后還能幫助我們stop或者unbind。這里只給出Test Class的代碼,完整的代碼在這里。

@MediumTest
@RunWith(AndroidJUnit4.class)
public class LocalServiceTest {
    @Rule
    public final ServiceTestRule mServiceRule = new ServiceTestRule();

    @Test
    public void testWithBoundService() throws TimeoutException {
        // Create the service Intent.
        Intent serviceIntent =
                new Intent(InstrumentationRegistry.getTargetContext(), LocalService.class);

        // Data can be passed to the service via the Intent.
        serviceIntent.putExtra(LocalService.SEED_KEY, 42L);

        // Bind the service and grab a reference to the binder.
        IBinder binder = mServiceRule.bindService(serviceIntent);

        // Get the reference to the service, or you can call public methods on the binder directly.
        LocalService service = ((LocalService.LocalBinder) binder).getService();

        // Verify that the service is working correctly.
        assertThat(service.getRandomInt(), is(any(Integer.class)));
    }
}

Test Broadcast

Android沒有為Broadcast提供類似ServiceTestRule這樣的Rule,因為這根本不需要。一般如果需要測試一個Broadcast,則直接創建這個這個receiver的實例,然后調用onReceive()方法,最后驗證一些信息。

public class LocalReceiverTest {
    @Test
    public void testOnReceive() throws Exception {
        LocalReceiver localReceiver = new LocalReceiver();
        Intent intent = new Intent();
        intent.putExtra("key", "I Love You!");//onReceive() just saved the key to somewhere
        localReceiver.onReceive(InstrumentationRegistry.getTargetContext(), intent);
        //do some asserts
        assertEquals(getKeyFromSomeWhere(), "I Love You!");
    }  
}

Test ContentProvider

ContentProvider的測試比較特殊,我們需要在獨立的測試環境中測試從而不影響真實的用戶數據。具體如何測試,Android官方給了詳細說明。想要查看例子可以看這里

Test File or DataBase

文件或者數據庫的測試需要注意兩個測試之間不能相互影響,且不能影響到正常的數據庫。這就要求我們自己建立一個測試環境,好在使用RenamingDelegatingContext能夠滿足上面的要求。RenamingDelegatingContext會將其他操作委托給一個給定的context對象,但在執行數據庫/文件相關的操作時,會用一個prefix重命名給定的數據庫名或者文件名。所以使用這個context,我們操作的只是一個測試數據庫或者文件。

public class MyDatabase extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "database.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabase(Context context){
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db){
        // some code
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // some code
    }
}
public class MyDatabaseTest {
    private MyDatabase db;

    @Before
    public void setUp() throws Exception {
        RenamingDelegatingContext context = new RenamingDelegatingContext(InstrumentationRegistry.getTargetContext(), "test_");
        //每次測試時,刪除舊的database文件
        context.deleteDatabase("database.db");
        //使用這個context創建database,文件名對應為'test_database.db'
        db = new MyDatabase(context);
    }

    @After
    public void tearDown() throws Exception {
        db.close(); 
    }

    //@Test
    public void testAddEntry(){
        // Here i have my new database wich is not connected to the standard database of the App
        ...
    }
}

UI Automator Test

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容