Android自動化測試-從入門到入門(5)AdapterView的測試

在以前的文章中,咱們簡單介紹了Espresso的使用。經過onView()方法咱們能夠快速定位到界面上咱們須要測試的目標元素。總體來講,onView()比較適用於UI比較簡單的狀況,在不須要過於複雜的匹配條件的狀況下是很方便的。可是,對於相似ListView這種有UI複用的元素來講,只是經過onView()就顯得複雜了一點,咱們來看一下針對這種狀況應有何種方案。segmentfault

AdapterView

AdapterView是一種經過Adapter來動態加載數據的界面元素。咱們經常使用的ListView, GridView, Spinner等等都屬於AdapterView。不一樣於咱們以前提到的靜態的控件,AdapterView在加載數據時,可能只有一部分顯示在了屏幕上,對於沒有顯示在屏幕上的那部分數據,咱們經過onView()是沒有辦法找到的。app

對於AdapterViewEspresso提供了以下方法用來查找元素:ide

/**
 * 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匹配的匹配規則;ui

  • Matcher<? extends Object>: 構造一個針對於Object(數據)匹配的匹配規則。this

從以上對比能夠看出,咱們在使用onData()方法對AdapterView進行測試的時候,咱們的思路就轉變成了首先關注這個AdapterView的具體數據,而不是UI上呈現的內容。固然,咱們最終的目標仍是要找到目標的UI元素,可是咱們是經過其數據源來進行入手的。設計

尋找數據

那麼,接下來,咱們就要學習如何去尋找咱們須要的數據了!顯然,要想找到咱們須要的數據,就須要構造一個onData()所使用的Matcher對象,而這個對象的構造和使用實際上和以前咱們所用的針對於ViewMatcher大概雷同。好比,咱們能夠指定單一條件:

onData(is(instanceOf(MyObject.class)))

表示咱們須要找一個AdapterView,其數據源的類型是MyObject(這是一個自定義的類)。固然了,咱們確定仍是須要更加精確地去尋找一個AdapterView中的指定條目,因而咱們能夠採用allOf()來構造一個符合匹配條件:

onData(allOf(is(instanceOf(MyObject.class)), myCustomMatcher()))

如上代碼便使用allOf()方法構造了一個符合匹配規則(allOf()方法能夠參考第三篇文章Espresso入門裏的介紹)。而上面的myCustomMatcher()方法構造了一個自定義的Matcher,咱們能夠採用本身的自定義Matcher來更加精準地進行數據的匹配。

自定義Matcher

接下來咱們要感覺一下自定義Matcher的強大之處了!爲了更好地給你們介紹自定義Matcher,我舉一個答疑君APP裏面的例子來進行說明。

在答疑君APP的老師頁面,有一個老師搜索的功能。當我點擊搜索框時,界面上便會顯示以前的搜索關鍵字歷史。如今,我須要在這個搜索關鍵字列表中點擊相應的關鍵字來觸發搜索。

簡單來講,個人目的就是:在搜索歷史ListView中點擊搜索關鍵字爲TEXT的條目。

首先,個人ListView的數據源類型爲List<SearchItem>,因而我先構造一個數據類型匹配條件:

is(instanceOf(SearchItem.class))

這個構造條件就指定了列表的數據源爲SearchItem類型。請注意,Espresso在根據onData()進行類型匹配時,是根據咱們的Adapter.getItem()方法返回的數據類型進行匹配的。若是咱們本身實現了一個自定義的Adapter,請注意咱們構造的匹配規則要和getItem()方法返回的數據類型相統一。

接下來,我就須要去找那個含有TEXT關鍵字的數據項了。個人SearchItem類的定義極其簡單:

public class SearchItem {
    private String keyword;
    
    public SearchItem() {}
    
    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }
    
    public String getKeyword() {
        return keyword;
    }
}

接下來我只要找到那個keywordTEXTSearchItem數據項就能夠了。爲此,我構造了以下的一個自定義Matcher:

/**
 * 查找指定關鍵字的搜索條件
 * @param name 須要搜索的關鍵字
 */
public static Matcher<Object> teacherSearchItemWithName(final String name) {
    return new BoundedMatcher<Object, SearchItem>(SearchItem.class) {
        @Override
        protected boolean matchesSafely(SearchItem item) {
            return item != null
                    && !TextUtils.isEmpty(item.getKeyword())
                    && item.getKeyword().equals(name);
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("SearchItem has Name: " + name);
        }
    };
}

接下來對該方法作一些說明,以助於幫助你們構造本身的Matcher:

1. @return Matcher<Object>

很顯然,返回值必須是一個Matcher<Object>對象,表明一個針對於Object數據的匹配規則。這也是onData()方法入參的要求。

2. BoundedMatcher

以上方法其實是構造了一個BoundedMatcher,咱們先來看一下BoundedMatcher的定義:

/**
 * Some matcher sugar that lets you create a matcher for a given type
 * but only process items of a specific subtype of that matcher.
 *
 * @param <T> The desired type of the Matcher.
 * @param <S> the subtype of T that your matcher applies safely to.
 */
public abstract class BoundedMatcher<T, S extends T> extends BaseMatcher<T> {
    // ...
    protected abstract boolean matchesSafely(S item);
    // ...
}

由以上定義咱們能夠看到,BoundedMatcher爲咱們指定了一個針對目標類型的子類型進行匹配的匹配規則。好比,咱們如今須要一個Matcher<Object>對象,但實際上咱們須要考察的目標類型是SearchItem,而SearchItem又是Object的子類,所以,咱們能夠經過BoundedMatcher來構造這個Matcher<Object>對象,只不過咱們實際上進行檢查的轉變成了SearchItem類型,只要採用以下寫法:

return new BoundedMatcher<Object, SearchItem>(SearchItem.class) {...}

3. matchesSafely()

上述複寫的matchesSafely()方法即是真正執行匹配的地方了!你們能夠看到,我由BoundedMatcher指定了SearchItem類型,所以matchesSafely()方法也接收了SearchItem類型的入參,咱們只要去考察入參提供的這個SearchItem對象是否符合咱們的匹配條件便可:

return item != null
       && !TextUtils.isEmpty(item.getKeyword())
       && item.getKeyword().equals(name);

在如上代碼中,我作了三步檢查:

  • 指定SearchItem自己不爲null

  • 指定SearchItemkeyword不爲空;

  • 指定SearchItemkeyword和咱們須要匹配的name相同。

只有符合這三個條件,咱們纔會認爲當前的SearchItem數據項符合咱們的預期。

綜合以上,將以前的兩個Matcher複合一下,我即可以構造以下的符合匹配規則了:

onData(allOf(is(instanceOf(SearchItem.class)), teacherSearchItemWithName(TEXT)))

這樣一來,我就可以成功地在個人搜索歷史列表中找到關鍵字爲TEXT的數據項了。

指定AdapterView

這樣就完了嘛?是的,針對自定義Matcher就已經講完了。實際上我在運行以上腳本的時候,Espresso仍是給我報了個AmbiguousViewMatcherException的異常。這是由於,答疑君APP的佈局比較複雜,在當前的View Hierarchy中有好幾個AdapterView,我須要指定我要進行匹配的AdapterView究竟是哪個。

Espresso提供了以下方法來完成這件事情:

/**
 * Selects a particular adapter view to operate on, by default we operate on any adapter view
 * on the screen.
 */
public DataInteraction inAdapterView(Matcher<View> adapterMatcher){}

inAdapterView()可讓咱們指定咱們須要匹配哪一個AdapterView。個人搜索歷史列表的id爲teacher_page_search_history_list,所以,我只要在上面的基礎上增長以下一行:

onData(allOf(is(instanceOf(SearchItem.class)), teacherSearchItemWithName(TEXT)))
.inAdapterView(withId(R.id.teacher_page_search_history_list))

便解決了問題。如今,Espresso只會針對個人搜索歷史列表進行數據匹配了!

關於如何抉擇

到目前爲止,咱們介紹了onView()onData()的使用。從以上的文章中,相信你們也可以感覺到這兩種匹配思路的設計目的與區別。在咱們平時的測試腳本編寫的過程當中,我我的仍是建議,一切都要按照咱們本身的實際狀況來進行方法的選擇。

實際上,雖然onData()方法是針對AdapterView來進行測試的,可是在答疑君的測試腳本中,有時針對AdapterView我也會採用onView()方法直接去進行匹配,由於有些簡單的場景實際上是不須要那麼複雜的數據分析的,只關注於UI上的顯示我也可以找到ListView中的某個控件。話說回來,Espresso只是一個工具,至於具體如何去用,就看咱們本身的發揮啦!

附錄

Android自動化測試-從入門到入門(1) Hello Testing!
Android自動化測試-從入門到入門(2) Testing APIs
Android自動化測試-從入門到入門(3) Espresso入門
Android自動化測試-從入門到入門(4) uiautomatorviewer
Android自動化測試-從入門到入門(5) AdapterView的測試
Android自動化測試-從入門到入門(6) 會玩的Espresso
Android自動化測試-從入門到入門(7) UI Automator

相關文章
相關標籤/搜索