<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<view> getcurrentviews()
擷取目前界面或彈框中所有的控件
arraylist<t> getcurrentviews(class<t>
classtofilterby)
擷取目前界面或彈框中所有控件類型為classtofilterby的控件
classtofilterby, view parent)
擷取父控件parent下所有控件類型為classtofilterby的控件
擷取目前界面或彈框中所有控件類型為textview的控件:
arraylist<textview>
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() <
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按鈕在開始标簽<a href="/login" class="btn btn-block
primary">與結束标簽</a>内,是以整體屬于一個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<webelement> getcurrentwebelements()
擷取目前webview的所有webelement元素
arraylist<webelement> 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<webelement>
webelements = solo.getcurrentwebelements();
webelement
webelement = null;
for(int
i=0;i< 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
&& (applicationinfo.flags &
applicationinfo.flag_system) ==1){
logutils.logd(tag,
"applicationinfo flag:" + (applicationinfo.flags &
applicationinfo.flag_system));
return true;
} catch (namenotfoundexception e) {
}
return false;