天天看點

騰訊Android自動化測試實戰3.1.4 Robotium的控件擷取、操作及斷言

<b>3.1.4 robotium的控件擷取、操作及斷言</b>

robotium是一款在android用戶端中的自動化測試架構,它需要模拟使用者操作手機螢幕。要完成對手機的模拟操作,應該包含以下幾個基本操作:

(1)需要知道所要操作控件的坐标。

(2)對要操作的控件進行模拟操作。

(3)判斷操作完成後的結果是否符合預期。

是以,本節将從控件擷取、控件操作及操作後斷言來介紹robotium,此外,由于webview在控件擷取和控件操作上都與native完全不同,将對其做單獨介紹。

1. native控件擷取

從robotium中擷取native控件主要有兩大方式:一個是根據被測應用的控件id來擷取;另一個是先擷取目前界面所有的控件,對這些控件進行過濾封裝後再提供相應的擷取控件的api。

1)根據被測應用的控件id來擷取

根據控件id擷取見表3-1。

根據string型id擷取控件:

imageview micon

= (imageview) solo.getview("mypic");

在android中,所有的控件都繼承自view,是以,如果被測應用中的控件有唯一id的話,就可以使用這種通過id形式唯一擷取所要操作的控件。

例如擷取relativelayout或linearlayout:

relativelayout

rel = (relativelayout) solo.getview("example1");

linearlayout lin

= (linearlayout) solo.getview("example2");

由于android中所有的控件都繼承自view類,而對于開發人員的自定義控件,這些自定義控件也基本是繼承自android的基礎控件擴充而來的,是以通過這種方式幾乎可以獲得所有類型的控件,擷取相應類型的控件時隻要進行轉義即可,是以當控件擁有唯一id時,推薦使用該方式。

控件id可以通過android sdk中提供的工具來檢視,例如%android_home%\tools\uiautomatorviewer.bat工具,在android 4.3及以上系統版本的手機上,可直接檢視到ui界面的id。

2)根據控件類型的索引、文本來擷取

根據文本擷取見表3-2。

此方式是robotium先将目前界面中的所有控件全部擷取,然後按控件類型、索引進行過濾後再擷取指定的控件view。

根據index索引擷取控件:

//傳回界面中第一個類型為button的控件

button loginbtn

= (button) solo.getbutton(0);

其他的如getedittext(int index)、gettext(int index)均同理。

根據文本text擷取控件:

//傳回界面中文本為‘登入’類型為button的控件

= (button) solo.getbutton("登入");

其他的如gettext(string text)、getedittext(string text)均同理。

在robotium中查找控件時,如果找不到相應id或文本的控件,測試架構會throw出“view with id ××× is no found”或者“with text ××× no found”等throwable異常,若我們并不希望是以而報錯,則可以使用try catch throwable來捕獲。

3)根據控件類型進行過濾

根據類型過濾見表3-3。

表3-3 根據類型過濾

傳回值  方法及說明

arraylist&lt;view&gt;      getcurrentviews()

擷取目前界面或彈框中所有的控件

arraylist&lt;t&gt;     getcurrentviews(class&lt;t&gt;

classtofilterby)

擷取目前界面或彈框中所有控件類型為classtofilterby的控件

classtofilterby, view parent)

擷取父控件parent下所有控件類型為classtofilterby的控件

擷取目前界面或彈框中所有控件類型為textview的控件:

arraylist&lt;textview&gt;

alltextviews = solo

              .getcurrentviews(textview.class);

擷取指定父控件下所有控件類型為textview的控件:

              .getcurrentviews(textview.class,

rel);

同樣是過濾出指定的控件類型,不過該方法是從父視圖parent中開始過濾,當不指定parent,即solo.getcurrentviews(textview.class,null)時,則和solo.getcurrentviews(textview.class)一樣,傳回的是目前界面中所有的。

移動app一般節奏很快,ui布局結構也經常随着功能的變更而變動,例如“登入”按鈕從最上面變到了最下面,是以通過索引或文本來擷取控件是有很大隐患的。很多時候,通過巧妙地控件過濾可以更準确地找到相應的控件。

2. native控件操作

對于android端的自動化測試而言,當我們擷取到期望的控件後,接下來就是對該控件進行點選、長按、文本輸入、拖動等模拟操作。除此之外,ui自動化測試為了貼近使用者的真實使用及自身健壯性,還需要時延等待、頁面加載等待;為了判斷界面是否符合預期,則還需要控件搜尋、界面截圖等操作。

1)點選、長按操作

點選長按見表3-4。

表3-4 點選長按

void      clickonview(view view)/clicklongonview(view view)

點選指定的view控件/長按指定的view控件

void      clickonscreen(float x, float y)/clicklongonscreen(float x, float y)

根據坐标x, y點選螢幕/根據坐标x, y長按螢幕

robotium是基于控件的自動化測試架構,當擷取到要操作的控件後,直接對控件進行點選、長按或文本輸入等操作即可。

點選指定的view控件:

= (button) solo.getview("loginbtn");

solo.clickonview(loginbtn)

robotium還提供了點選文本、點選圖檔的api,例如clickontext(string text)、click-onbutton(string text)等,這類api類似于前文所介紹的,先根據文本擷取控件,再發送點選事件:

類似于點選、長按指定的view控件:

solo.clicklongonview(loginbtn)

需要注意的是,robotium的點選事件是通過instrumentation發送的,是以該類點選方法不能點選非被測應用的區域,例如不能點選至通知欄所在的區域,否則會出現類似如下的異常:

java.lang.securityexception:

"injecting to another application requires inject_events permission"

是以在使用robotium編寫測試用例時,需要注意其無法跨應用的缺點,進而盡量避免出現此場景,有些場景偶然性地無法規避,可以采用try catch throwable的形式捕獲異常,而對于需要跨應用的場景,則可以使用9.4.2節介紹的ui automator結合instrumentation模式進行處理。

try {

    } catch (throwable e) {

}

在手機設定–開發者選項中,可以開啟“指針位置”,開啟“指針位置”後,再觸摸螢幕時,可實時顯示螢幕坐标。調試時為了更準确地知道對螢幕的什麼地方進行了操作,也常常同時開啟“顯示觸摸操作”開關。

2)操作輸入框

操作輸入框見表3-5。

表3-5 操作輸入框

void      entertext(edittext edittext, string text)

在指定的edittext中輸入文本text

void      typetext(edittext edittext, string text)

在指定的edittext中鍵入文本text

void      clearedittext(edittext edittext)

清空指定的輸入框

在自動化測試過程中,當我們可以準确擷取控件,并能模拟點選、長按等基本操作後,就可以在被測應用中進行自由跳轉,然後可能就需要進行一些輸入操作。測試架構中主要提供了entertext(edittext edittext, string text)和typetext(edittext edittext, string text)兩種方法,前者直接對edittext文本框進行指派,不會有文本輸入的展示過程,而後者則會一個文本一個文本地輸入,更貼近真實使用者的操作。

edittext useret

= (edittext) solo.getview("example_et_id");

solo.entertext(useret,

"my_user_name")      //直接對文本框指派

solo.typetext(useret,

"my_user_name")       //會展示輸入的過程

3)滑動、滾動

滑動、滾動見表3-6。

表3-6 滑動、滾動

void      drag(float fromx, float tox, float fromy,

float toy, int stepcount)

從起始x,y坐标滑至終點x,y坐标;通過stepcount參數指定滑動時的步長

void      scrolltotop() / scrolltobottom()

滾動至頂部 / 滾動至底部

void      scrollup() / scrolldown()

向上滾動螢幕 / 向下滾動螢幕

void      scrolllisttoline(abslistview abslistview,

int line)

滾動清單至第line行

在android中,常用的操作還有各種滑動手勢,如上拉、下拉、左滑、右滑等。在滑動方面,測試架構主要提供了兩類支援,一類是根據坐标進行滑動進而可以模拟各類手勢操作,另一類則是根據控件來直接進行滾動操作。

根據坐标進行滑動的主要是drag(float fromx, float tox, float fromy, float toy, int step count),這裡的參數包括起始位置的x與y坐标、終點位置的x與y坐标,還有步長stepcount。其中步長stepcount的意思是,假如要從a點滑到b點,如果步長為1,那麼将直接産生從a點到b點的手勢操作,滑動速度很快;如果步長為100,則将從a到b分成100等份,例如a、a1、a2…b,然後依次從a滑到a1,再從a1滑到a2、a2滑到a3……這樣滑動更慢但結果也更精确,例如當我們在手機上快速從下往上滑動時,清單滑動是有慣性的,會快速滾動,而這常常不是我們所需要的。

根據控件進行滾動主要有滾動至頂部、底部等方法。scrolltotop()方法可以将目前螢幕滑至頂部,如果目前是listview則滑至清單的頂部,如果是webview則滑至頁面的頂部。同樣地,scrolltobottom()可将界面滑至底部。類似的還有向下滑一屏的scrolldown()方法和向上滑一屏的scrollup()方法。與前文介紹的drag方法不同的是,這類滾動調用的是相應控件自身的api,例如webview的滾動調用的是控件自身的pageup(boolean top)或pagedown(boolean bottom)方法。是以,這種方式與drag方式最大的差別在于,drag是實際地模拟手勢操作,當上拉時,如果listview有監聽上拉加載更多,那麼使用drag是可以觸發上拉加載更多的,而scrollup()則不能。

4)搜尋與等待

搜尋與等待見表3-7。

表3-7 搜尋與等待

void      sleep(int time)

休眠指定的時間,機關毫秒

boolean searchtext(string text)

從目前界面搜尋指定文本

boolean waitforview(int id) / waitfortext(string text)

等待指定控件出現 / 等待指定文本出現

boolean waitforactivity(string name)

等待指定的activity出現

boolean waitforlogmessage(string logmessage)

等待指定的日志資訊出現

boolean waitfordialogtoopen() / waitfordialogtoclose()

等待彈框打開 / 等待彈框關閉

ui自動化測試常常被诟病運作不穩定,除了項目快速疊代導緻界面經常變更這一不可控因素外,腳本常常運作出錯就是由于沒有合适的等待機制導緻控件未找到、點選異常等問題,要想測試用例能夠快速且穩定地運作,合理使用等待是關鍵要素之一。

robotium中提供了諸多與等待相關的api,但是實際情況中需要等待的操作往往要複雜得多,是以測試架構中也提供了condition模式,即waitforcondition(condition condition, int timeout)方法,使用該方法時,實作condition接口并重寫issatisfied()方法,issatisfied()為true時将跳出等待。通過這種模式我們可以自定義實作更多類型的等待操作,如代碼清單3-1所示。

代碼清單3-1 使用waitforcondition模式實作等待

public void

waitforappinstalled(final string appname, int timeout) {

    waitforcondition(new condition() {

        @override

        public boolean issatisfied() {

            sleeper.sleepmini();

            return checker.isappinstalled(appname);

        }

    }, timeout);

當然了,我們也可以使用逾時機制來實作,如代碼清單3-2所示。

代碼清單3-2 使用timeout模式實作等待

    long endtime = systemclock.uptimemillis() +

timeout;

    while (systemclock.uptimemillis() &lt;

endtime) {

        if (checker.isappinstalled(appname)) {

            break;

        sleeper.sleep();

    }

需要注意的是,robotium中查找控件、點選控件等api都預設使用了搜尋與等待機制,當我們使用上文提到的擷取控件、點選控件相關操作時,測試架構已經做好了等待操作,是以非特殊情況是不需要額外增加等待操作的步驟的。太多的等待将使用例執行變得緩慢低效,是以在用例編寫調試過程中應該做好平衡。

5)截圖及其他

截圖及其他見表3-8所示。

表3-8 截圖及其他

void      takescreenshot(string name)

截圖,圖檔名稱為指定的name參數,圖檔預設路徑為/sdcard/robotium-screenshots/

void      finishopenedactivities()

關閉目前已打開的所有activity

void      goback() / gobacktoactivity(string name)

點選傳回鍵 / 不斷地點選傳回鍵直至傳回到指定的activity

void      hidesoftkeyboard()

收起鍵盤

void      setactivityorientation(int orientation)

等待設定activity轉屏方向

自動化測試過程中,因為都是自動化執行的,當用例執行失敗時,除了日志外,最友善解決定位問題的就是運作時的截圖,有了截圖定位問題往往事半功倍,robotium中提供了單次截圖及截取一系列圖檔的功能。takescreenshot()方法可以直接截取目前螢幕,并将其預設地儲存在/sdcard/robotium-screenshots/目錄下,要更改圖檔名稱則使用takescreenshot(string name),要截取某時間段内一個序列的話則可以使用startscreenshotsequence(string name)。那麼如何更好地在自動化中使用截圖功能呢?一般情況下我們更希望的是在用例執行失敗時進行截圖,詳情請見本書9.3.2節中介紹的結合spoon出錯重試與截圖。

除了正常的操作外,robotium測試架構還提供了發送模拟按鍵sendkey(int key)、設定螢幕是橫屏還是豎屏setactivityorientation(int orientation)、模拟點選傳回鍵goback()、跳轉至指定activity的方法gobacktoactivity(string name)、收起輸入法hidesoftkeyboard()、關閉所有已打開的activity 的方法finishopenedactivities()等。通過組合利用這些常用操作,基本就可以完成在android端的ui自動化操作了。

3. webview支援

在android app中由于html可以更快地響應變化,而不像native那樣需要釋出版本才能讓使用者使用上新特性,是以大多數app都是既有native部分,也有html部分,也即俗稱的hybrid app。而robotium在robotium4.0版本中就開始全面支援webview的自動化了。要了解如何使用robotium測試架構來對app中的webview部分進行自動化測試,首先需要了解html基礎,然後了解robotium是如何擷取頁面元素并進行操作的。

1)html基礎

robotium支援通過id、classname等方式來擷取webelement元素,是以,首先了解id、classname等的概念,模拟打開github首頁并檢視網頁源碼如圖3-12所示。

html元素:指的是從開始标簽到結束标簽的所有代碼。如圖3-12所示,sign in按鈕在開始标簽&lt;a href="/login" class="btn btn-block

primary"&gt;與結束标簽&lt;/a&gt;内,是以整體屬于一個html元素。

html屬性:屬性總是以名稱/值對的形式出現的,比如:name="value"。屬性總是在html元素的開始标簽中規定的。核心屬性有class(規定元素的類名)、id(規定元素的唯一id)。sign in按鈕中就有class屬性,class="btn btn-block primary"。

圖3-12 github首頁的html結構

2)webelement相關api及操作

webelement相關api見表3-9。

表3-9 webelement相關api

arraylist&lt;webelement&gt; getcurrentwebelements()

擷取目前webview的所有webelement元素

arraylist&lt;webelement&gt; getcurrentwebelements(by by)

通過by根據指定的元素屬性擷取目前webview的所有webelement元素

void      clickonwebelement(by by)

通過by根據指定的元素屬性點選webelement

void      clickonwebelement(webelement webelement)

點選指定的webelement

void      entertextinwebelement(by by, string text)

根據by找到webelement,并輸入指定的文本text

boolean waitforwebelement(by by)

等待根據by獲得的webelement出現

在robotium中對webelement進行操作有兩種方式,一種是先擷取相應的webelement,然後發送點選事件,另一種則是直接調用clickonwebelement(by by)進行點選。

在擷取webelement元素前我們首先需要知道這個頁面的html結構,需要知道url連結才能友善地檢視html元素、屬性等。

擷取webview中的頁面資訊可以參考本書6.3.3節appium 腳本常見問題及處理方法中如何擷取webview中的頁面資訊這一部分内容,通過chrome浏覽器中的devtools工具可以快速友善地檢視webview中的資訊。

我們也可以使用原始的如代碼清單3-3所示的方式列印出所有的元素資訊。

代碼清單3-3 使用日志列印方式擷取元素資訊

arraylist&lt;webelement&gt;

webelements = solo.getcurrentwebelements();

webelement

webelement = null;

for(int

i=0;i&lt; webelements.size();i++){

    webelement = webelements.get(i);

    log.i("webelement",

"getid:" + webelement.getid());

log.i("webelement","getclassname:"+webelement.getclassname());

    log.i ("webelement",

"gettext:" + webelement.gettext());

當我們知道了相應頁面的元素、屬性後,就可以通過元素或屬性等資訊來擷取指定的webelement。

1)擷取目前webview所有webelement

2)通過classname擷取

signins = solo.getcurrentwebelements(by

             .classname("btn btn-block

primary"));

3)通過id擷取

             .id("example_id"));

4)通過textcontent擷取

             .textcontent("sign

in"));

類似的還有通過cssselector、name、tagname、xpath等方式擷取。

5)通過webelement點選

拿到webelement後,如果在頁面中該辨別是唯一的,那麼數組長度為1,可以通過clickonwebelement(webelement

webelement)方法比較精确地點選。

solo.clickonwebelement(signins.get(0));

以上擷取webelement并點選也可以直接使用clickonwebelement(by by)方法完成。

solo.clickonwebelement(by.classname("btn

btn-block primary"));

6)webelement輸入

solo.entertextinwebelement(by.name("userid"),

"your username"); 

solo.entertextinwebelement(by.name("passwd"),

"your passwd"); 

同樣地,webelement也支援等待操作,可以通過waitforwebelement(by by)等待相應的元素出現,然後查找,這樣可以使腳本更健壯。不過robotium中的clickontx-webelement(by by)也均預設已經使用了等待機制,是以非特殊情況,腳本中不需要額外增加等待操作。

robotium中對webview的支援由于是使用對系統webview執行js進而封裝擷取頁面元素的方式,是以該測試架構隻支援app中使用系統webview的情況,如果app或浏覽器使用的是非系統核心的webview,例如騰訊手機qq浏覽器的x5核心,則無法使用,需要引用x5的sdk并對robotium進行改造才可支援。

4. 斷言

自動化測試中,我們擷取控件、執行操作後,接下來就是要對操作後的場景進行斷言了。robotium是基于instrumentation的測試架構,其測試用例編寫的架構是基于junit的,是以,本小節将先介紹junit中的斷言,然後介紹robotium中适用于android端自動化的斷言。

1)junit中的斷言

junit中的斷言相關api見表3-10。

表3-10 junit中的斷言相關api

void      asserttrue(string message, boolean

condition)

斷言傳入的condition參數應該為true,否則将抛出一個帶有message提示的throwable異常

void      assertfalse(string message, boolean

斷言傳入的condition參數應該為false,否則将抛出一個帶有message提示的throwable異常

void      fail(string message)

直接使用例失敗,并抛出一個帶有message提示的throawable異常

junit中的斷言可以檢視android sdk中junit.framework.assert包下的assert類,常用的有asserttrue(string message, boolean condition)方法,即斷言方法中第二個參數condition的結果是否為true,如果為true則該語句執行通過,否則該語句将抛出throwable的異常,而異常中的提示語将為第一個參數message。是以,使用斷言時,應該準确明了地說明message參數,以便斷言不符合預期時可以快速判斷是什麼原因導緻的。例如斷言某個控件應該要顯示在界面中,代碼如下:

asserttrue("‘登入’按鈕應該要顯示在界面", loginbtn.isshown());

同樣地,還有assertfalse(string message, boolean condition)方法,用于斷言第二個條件中的結果應該為false。通過這兩個方法,隻要測試過程中的預期結果能轉換成true或false的都可以進行判斷,例如判斷界面元素是否顯示、數值大小比較、文本對比等。

在測試工程中,當出現某種場景時,有時我們希望直接使用例失敗而不再往下執行,此時可以使用assert類中的fail(string message)方法,例如:

if(isbadhappened()){

     fail("this should no happened");

而如果出現某種場景,我們希望直接使用例通過而不再執行,則此時在用例腳本中直接使用return即可。

2)robotium中的斷言

robotium中的斷言相關api見表3-11。

表3-11 robotium中的斷言相關api

void      assertcurrentactivity(string message,

string name)

斷言目前界面是否為name參數指定的activity,若不是則抛出一個帶有message提示的throwable異常

void      assertmemorynotlow()

斷言目前是否處于低記憶體狀态

robotium基于junit中的斷言判斷,也封裝了幾個友善在android端自動化時使用的斷言方法。例如assertcurrentactivity(string message, string name)方法可以判斷目前界面是否是預期的activity,我們知道android中許多頁面都對應于一個activity,當app跳轉到一個界面時,就可以使用該方法來判斷是否已跳轉到相應activity了。

//擷取目前的activity名

string

currentactivity =

              solo.getcurrentactivity().getclass().getsimplename();

//

expectedactivity為期望跳轉的activity

solo.assertcurrentactivity("expected

xxxactivity" + " but was " + currentactivity, expectedactivity);

另外,測試架構中的assertmemorynotlow()方法可以用來判斷目前是否處于記憶體吃緊的情況。在robotium封裝的斷言api并不多,因為如前文所說,大多數場景都可以使用true或false來進行判斷。

3)android中的斷言

android中的斷言相關api見表3-12。

表3-12 android中的斷言相關api

void      assertonscreen(view origin, view view)

斷言view是否在螢幕中

void      assertbottomaligned(view first, view

second)

斷言兩個view是否底端對齊,即它們的底端y坐标相等

在android sdk中,android.test.viewasserts包下有個viewasserts類可以友善地進行與控件相關的斷言。例如斷言控件是否在視窗中assertonscreen(view origin, view view),斷言兩個控件是否底部對齊assertbottomaligned(view first, view second),是否右對齊assertrightaligned(view first, view

second),等等。而之是以能實作這些斷言在于view控件本身就具有非常多的可以用于判斷自身狀态的屬性,例如view可以判斷自身是否顯示isshown(),判斷是否被選中isselected(),還可以擷取自身所在的坐标位置getlocationonscreen(int[]

location)和寬高getwidth()、getheight(),等等。由于基于robotium編寫的測試用例是以app形式安裝進手機的,且運作時是運作在被測應用所在的程序,是以我們使用斷言時,可以借助android sdk中豐富的類庫來進行各種判斷,例如判斷目前網絡狀态、應用安裝情況、目前應用是否處于前台等,可以很友善地對測試的預期結果進行判斷。如代碼清單3-4所示,調用android中的api根據包名判斷是否是系統應用。

代碼清單3-4 根據包名判斷是否是系統應用

/**

 * 根據packagename判斷該應用是否是系統應用

 * @param packagename  應用的包名

 * @return 

true,系統應用;false,非系統應用

 */

public boolean

issystemapp(string packagename){    

    packagemanager pm =

getinstrumentation().gettargetcontext().getapplicationcontext().getpackagemanager();

    applicationinfo applicationinfo = null;

    try {

        applicationinfo =

pm.getapplicationinfo(packagename, packagemanager.get_uninstalled_packages);

        if(applicationinfo !=null

&amp;&amp;  (applicationinfo.flags &amp;

applicationinfo.flag_system) ==1){

        logutils.logd(tag,

"applicationinfo flag:" + (applicationinfo.flags &amp;

applicationinfo.flag_system));

            return true;

    } catch (namenotfoundexception e) {

    }      

    return false;

繼續閱讀