不錯的官方文檔翻譯(含作者見解)
這篇文章翻譯簡直太棒了,本文的內容都來自對該文章所記錄的筆記。
Android自動化測試--Espresso框架使用
Android官網,_科學上網也訪問不了跪求原因
如何使用Espresso
- 在module級別的Gradle中添加以下配置
android {
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
2.添加依賴
dependencies {
androidTestCompile 'com.android.support:support-annotations:23.1.1'
androidTestCompile 'com.android.support.test:runner:0.4.1'
androidTestCompile 'com.android.support.test:rules:0.4.1'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
}
3.測試代碼編寫
一般來說我們在main/java中放置主要的業務邏輯代碼,在 androidTest/java放置測試代碼.
src/
androidTest/java ----這里存放instrumentation test相關的代碼
main/java ----這里存放工程代碼
建議測試代碼的分類與被測試類放置在相同的包目錄下,例如待測試的類為src/main/java/package-name/A.java,那么測試類可以放置在src/androidTest/java/package-name/ATest.java目錄下,測試類名稱以Test作為后綴.
Espresso的一些語法
onView() 查找元素,其完整的方法簽名如下:
public static ViewInteraction onView(final Matcher<View> viewMatcher) {}
這個方法接收一個Matcher<View>類型的入參,返回一個ViewInteraction對象,其所做的事情就是根據Matcher<View>所指定的條件,在當前UI頁面上尋找符合條件的View,并且把相應的View返回出來。這樣說還是比較抽象,我們可以用一個具體的例子加以說明。
當我們在實現布局的時候,每個控件都會有一些特殊的屬性來確定其唯一性,比如最常用的R.id。Matcher<View>支持通過控件的唯一ID來從當前頁面上尋找目標控件,對應的方法為withId(),該方法定義如下:
public static Matcher<View> withId(final int id) {}
大家可以看到,該方法接收了一個int類型的入參,返回了一個Matcher<View>對象,于是,采用如下寫法:
onView(withId(id));
我們就能在當前頁面找到指定ID所對應的目標控件了。
實際上,Espresso提供了很多方法來讓我們自定義我們的查找條件。比如我們可以通過withText()方法來尋找顯示了指定文案的控件等等。具體支持的Matcher類型可以參考Espresso cheat sheet。
需要提醒大家一點的是,onView()方法在根據匹配條件進行查找時,它的目標是找到唯一的一個目標控件。如果我們制定的匹配條件有多個控件可以匹配(比如復用了layout的布局,或者顯示相同文字的TextView等),該方法會拋出一個AmbiguousViewMatcherException異常,因此我們在構造匹配條件時,一定要確保能查找到的目標控件是唯一的。
如果單一的匹配條件無法精確地匹配出來唯一的控件,我們可能還需要額外的匹配條件,此時可以用allOf()方法來進行復合匹配條件的構造:
onView(allOf(withId(id), withText(text)))
以上代碼可以查找ID為id同時顯示的文字內容為text的控件。這里需要注意的是,為了保證自動化測試的效率,我們應盡可能減少匹配條件的數量。如果用一個匹配條件能夠滿足我們的需求,我們也就沒有必要再用allOf()來構造復合匹配條件了。
操作元素
public ViewInteraction perform(final ViewAction... viewActions) {}
該方法定義在ViewInteraction類里面。還記得onView()方法的返回值么?yes,正是一個ViewInteraction對象。因此,我們可以在onView()方法找到的元素上直接調用perform()方法進行一系列操作:
onView(withId(id)).perform(click())
如上代碼對onView()查詢到的元素做了一次點擊的操作。請注意,perform()方法的入參是變長參數,也就意味著,我們可以依次對某個元素做多個操作:
onView(withId(id)).perform(click(), replaceText(text), closeSoftKeyboard())
以上代碼對目標元素依次做了點擊、輸入文本、關閉輸入法鍵盤的操作。這是一個典型的填寫表單的行為。
檢查結果
到目前為止,我們已經能找到元素,也能夠對元素進行一些操作了!接下來我們需要檢查一下這些操作的結果是否符合我們的預期。
Espresso提供了一個check()方法用來檢測結果:
public ViewInteraction check(final ViewAssertion viewAssert) {}
該方法接收了一個ViewAssertion的入參,該入參的作用就是檢查結果是否符合我們的預期。一般來說,我們可以調用如下的方法來自定義一個ViewAssertion:
public static ViewAssertion matches(final Matcher<? super View> viewMatcher) {}
這個方法接收了一個匹配規則,然后根據這個規則為我們生成了一個ViewAssertion對象!還記得Matcher這個類型么!!是的,這就是onView()方法的入參!實際上他們是同一個類型,其使用方法也是完全一致的。
比如,我想檢查一下指定id的TextView是否按照我的預期顯示了一段text文本,那么我就可以這樣寫:
onView(withId(id)).check(matches(withText(text)))
簡潔明了。ViewAssertion的支持也可以參照這個Espresso cheat sheet。
AdapterView
對于類似ListView這種有UI復用的元素來說,只是通過onView()就顯得復雜了一點,我們來看一下針對這種情況應有何種方案。
AdapterView是一種通過Adapter來動態加載數據的界面元素。我們常用的ListView, GridView, Spinner等等都屬于AdapterView。不同于我們之前提到的靜態的控件,AdapterView在加載數據時,可能只有一部分顯示在了屏幕上,對于沒有顯示在屏幕上的那部分數據,我們通過onView()是沒有辦法找到的。
對于AdapterView,Espresso提供了如下方法用來查找元素:
/**
* Creates an {@link DataInteraction} for a data object displayed by the application. Use this
* method to load (into the view hierarchy) items from AdapterView widgets (e.g. ListView).
*
* @param dataMatcher a matcher used to find the data object.
*/
public static DataInteraction onData(Matcher<? extends Object> dataMatcher) {}
我們首先來研究一下這個方法的返回值。從以上定義可以看出,該方法返回了一個DataInteraction對象,還記得onView()方法返回的ViewInteraction對象么?這兩者的區別可以大概描述為:
- ViewInteraction: 關注于已經匹配到的目標控件。通過onView()方法我們可以找到符合匹配條件的唯一的目標控件,我們只需要針對這個控件進行我們需要的操作。
- DataInteraction: 關注于AdapterView的數據。由于AdapterView的數據源可能很長,很多時候無法一次性將所有數據源顯示在屏幕上,因此我們主要先關注AdapterView中包含的數據,而非一次性就進行View的匹配。
我們再來研究一下這個方法的入參。從以上定義看出,該方法接收了一個Matcher<? extends Object>的參數,該參數用來指定一個匹配規則。還記得onView()的入參么?是一個Matcher<View>對象。從類型上來看,這兩者的區別也不言而喻:
- Matcher<View>: 構造一個針對于View匹配的匹配規則;
- Matcher<? extends Object>: 構造一個針對于Object(數據)匹配的匹配規則。
從以上對比可以看出,我們在使用onData()方法對AdapterView進行測試的時候,我們的思路就轉變成了首先關注這個AdapterView的具體數據,而不是UI上呈現的內容。當然,我們最終的目標還是要找到目標的UI元素,但是我們是通過其數據源來進行入手的。
尋找數據
那么,接下來,我們就要學習如何去尋找我們需要的數據了!顯然,要想找到我們需要的數據,就需要構造一個onData()所使用的Matcher對象,而這個對象的構造和使用實際上和之前我們所用的針對于View的Matcher大概雷同。比如,我們可以指定單一條件:
onData(is(instanceOf(MyObject.class)))
表示我們需要找一個AdapterView,其數據源的類型是MyObject(這是一個自定義的類)。當然了,我們肯定還是需要更加精確地去尋找一個AdapterView中的指定條目,于是我們可以采用allOf()來構造一個符合匹配條件:
onData(allOf(is(instanceOf(MyObject.class)), myCustomMatcher()))
如上代碼便使用allOf()方法構造了一個符合匹配規則。而上面的myCustomMatcher()方法構造了一個自定義的Matcher,我們可以采用自己的自定義Matcher來更加精準地進行數據的匹配。
//TODO