啟示
? ?iOS的自動(dòng)化測試相對安卓的成熟度底很多.而自動(dòng)化測試的目的是減輕人工測試的壓力.剛剛看一文章提到“沒有兩片相同的葉子”魂拦,也沒有兩次相同的手工測試.測試比搭,給人的一個(gè)錯(cuò)覺就是重復(fù)性瓦堵,包括有些公司在招測試人員的時(shí)候會(huì)有一條叫“能夠勝任重復(fù)性工作”菲嘴。但試問叭首,誰會(huì)手工運(yùn)行N次相同的測試呢谋梭?一個(gè)測試人員,他在執(zhí)行什么測試不重要哑姚,他為什么要執(zhí)行這個(gè)測試——背后的分析思考才重要祭饭。自動(dòng)化的測試,能夠替代的是相同的執(zhí)行叙量,卻無法替代人的分析與思考倡蝙。而軟件產(chǎn)品在研發(fā)迭代的過程,是團(tuán)隊(duì)不斷地把新的分析思考注入其中的過程绞佩。測試工作面對的是始終在不斷變化的測試需求寺鸥。所以測試的分析思考,包括手工和自動(dòng)化的測試也需要不斷地迭代改善品山。
我16年底接到老大的要求讓我探索自動(dòng)化測試.下面就是我研究了一個(gè)月的成果希望可以幫到剛剛踏入自動(dòng)化測試的童靴.
對于iOS的自動(dòng)化測試有很多種.其他的很多種都是需要比較多的時(shí)間來學(xué)習(xí)寫腳本的能力.而XCTest是Xcode自帶.本身的腳本學(xué)法可以用OC或者是Swift.個(gè)人推薦使用Xcode8的Swift語句.
實(shí)現(xiàn)
自從Xcode7的開始XCTest的UI測試就開始出現(xiàn).到Xcode8去掉UIAutoumator了.那說明XCTest在不久的將來會(huì)提供更多的接口或者被優(yōu)化.(-_-當(dāng)然也有可能會(huì)出更好的新插件).
上圖
上面的可以進(jìn)行單元測試和接口測試.而下面的則進(jìn)行UI測試.今天只對UI測試做詳細(xì)的介紹.對于單元測試各位上網(wǎng)查找一下對著用就行了.
在我們從xcode7開始都是默認(rèn)創(chuàng)建
如果說我們的工程吧這兩個(gè)文件刪了或者一開始沒有加進(jìn)去我們可以
得到了我們所要的頁面了
那我們要怎么去用其實(shí)很簡單.我們就點(diǎn)下面那個(gè)紅點(diǎn).那個(gè)就是錄制功能.進(jìn)入錄制的時(shí)候,我們點(diǎn)擊我們的模擬器或者真機(jī),它就會(huì)把我們的操作用代碼記錄下來.
如上圖的第四塊就是我們錄制生成的代碼.那我們又怎么運(yùn)行這些代碼呢.你可以使用快捷鍵CMD+U 或者你可以點(diǎn)擊第一塊的的藍(lán)色方塊.然后可以在第二塊或者第三塊的綠色打鉤的地方點(diǎn)擊.鼠標(biāo)指向這個(gè)地方會(huì)一個(gè)灰色的播放按鈕.就可以運(yùn)行腳本了.
XCTest的API
現(xiàn)在就是來學(xué)習(xí)XCTest的API.
第一 XCUIApplication
XCTest新加的類胆建,用于做UI測試,代表被測應(yīng)用肘交,父類為XCUIElement
主要的兩個(gè)方法
launch
啟動(dòng)應(yīng)用笆载。如果目標(biāo)應(yīng)用已運(yùn)行,首先終止應(yīng)用,然后再次啟動(dòng)應(yīng)用宰译。
terminate
關(guān)閉應(yīng)用。
兩個(gè)屬性
launchArguments
數(shù)組對象魄懂,保存啟動(dòng)參數(shù)沿侈。
launchEnvironment
字典對象,保存啟動(dòng)環(huán)境變量
實(shí)例
Swift版本
func testXCUIApplicationAPI(){
let app = XCUIApplication()
//關(guān)閉應(yīng)用
app.terminate()
//啟動(dòng)應(yīng)用(如果應(yīng)用已啟動(dòng),該方法會(huì)先關(guān)閉應(yīng)用市栗,再啟動(dòng)應(yīng)用)
app.launch()
//獲取啟動(dòng)參數(shù)
let args = app.launchArguments;
for arg in args {
print(arg);
}
//獲取啟動(dòng)環(huán)境變量
let envs = app.launchEnvironment;
for env in envs {
print(env);
}
}
OC版本
- (void)testXCUIApplicationAPI {
XCUIApplication *app = [[XCUIApplication alloc] init];
//關(guān)閉應(yīng)用
[app terminate];
//重新啟動(dòng)引用
[app launch];
//啟動(dòng)參數(shù)
NSArray *args = [app launchArguments];
for(int i=0;i<[args count];i++){
NSLog(@"arg :? %@",[args objectAtIndex:i]);
}
//啟動(dòng)環(huán)境
NSDictionary *env = [app launchEnvironment];
for (id key in env) {
NSString *object=[env objectForKey:key];
NSLog(@"env : %@",object);
}
}
第二 XCUIElement
XCUIElement 繼承自
NSObject缀拭,XCUIElementAttributes(定義了一些控件元素屬性,比如value,title等)填帽,XCUIElementTypeQueryProvider(一些元素集合的抽象蛛淋,比如代表按鈕的buttons),代表控件對象
方法
descendantsMatchingType
從該控件的后代控件中找到符合指定類型的控件(子子孫孫都認(rèn))篡腌,需要傳入XCUIElementType類型的參數(shù)褐荷,返回XCUIElementType類型的對象。
childrenMatchingType
只從該控件的孩子節(jié)點(diǎn)中找到符合指定類型的控件(只認(rèn)兒子)嘹悼,需要傳入XCUIElementType類型的參數(shù)叛甫,返回XCUIElementType類型的對象。
屬性
exists
判斷控件對象是否存在杨伙。BOOL類型其监。
debugDescription
保存某控件的debug信息,這些信息只能用于case的調(diào)試限匣,方便我們寫case抖苦,不能作為測試的數(shù)據(jù)。NSString類型米死。
擴(kuò)展XCUIElement
無論OC和Swift都是在XCUIElement的基礎(chǔ)上擴(kuò)展如下方法
tap
單擊
doubleTap
雙擊
twoFingerTap
雙指單擊
pressForDuration(duration: NSTimeInterval)
長按(Swift寫法)锌历,時(shí)間由傳入的參數(shù)定義,單位為秒
pressForDuration(duration: NSTimeInterval, thenDragToElement otherElement: XCUIElement)
長按拖拽(Swift寫法)峦筒。在控件上長按后辩涝,拖拽到另外一個(gè)控件。傳入2個(gè)參數(shù):長按時(shí)間和拖拽到目標(biāo)控件勘天。
swipeUp
控件上滑動(dòng)怔揩。從下劃到上
swipeDown
控件上滑動(dòng)。從上滑到上
swipeLeft
控件上滑動(dòng)脯丝。從右滑到左
swipeRight
控件上滑動(dòng)商膊。從左滑到右
typeText
輸入字符。需要一個(gè)參數(shù):NSString
實(shí)例
Swift版本
func testXCUIElementAPI() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let app = XCUIApplication()
//向上滑動(dòng)
app.swipeUp()
//向下滑動(dòng)
app.swipeDown()
//點(diǎn)擊“Groceries”列進(jìn)入子目錄
app.tables.staticTexts["Groceries"].tap()
//獲取當(dāng)前界面的表格對象
let tables = app.tables
//定位到編輯框"Add Item"
let addItemTextField = tables.textFields["Add Item"]
//點(diǎn)擊,焦點(diǎn)定位到編輯框
addItemTextField.tap()
//輸入Hello
addItemTextField.typeText("Hello")
//回車鍵
app.typeText("\r")
//獲取表格中的Cell對象宠进,每一個(gè)表格單元都是一個(gè)Cell對象
let cells = tables.childrenMatchingType(.Cell)
//獲取第二個(gè)單元晕拆,跳過了輸入框所在的行,XCUIElement對象
let cell = cells.elementAtIndex(1)
cell.swipeLeft()
cell.swipeRight()
//從第一個(gè)單元中找到文本框控件集合,XCUIElementQuery對象
let textFields = cell.childrenMatchingType(.TextField)
//let textFields = cell.descendantsMatchingType(.TextField)
//獲取第一個(gè)文本框控件实幕,XCUIElement對象
let textField = textFields.elementAtIndex(0)
if textField.exists{
//左滑然后右滑
textField.swipeLeft()
textField.swipeRight()
}
//長按5.5秒
textField.pressForDuration(5.5)
//打印debug的描述信息吝镣,也就是case執(zhí)行過程中的查找過程
print(textFields.debugDescription)
}
OC版本
-(void)testXCUIElementAPI {
XCUIApplication *app = [[XCUIApplication alloc] init];
//向上滑動(dòng)
[app swipeUp];
//向下滑動(dòng)
[app swipeDown];
//點(diǎn)擊“Groceries”列進(jìn)入子目錄
[app.tables.staticTexts[@"Groceries"] tap];
//獲取當(dāng)前界面的表格對象
XCUIElementQuery *tables = app.tables;
//定位到編輯框"Add Item"
XCUIElement *addItemTextField = tables.textFields[@"Add Item"];
//點(diǎn)擊,焦點(diǎn)定位到編輯框
[addItemTextField tap];
//輸入Hello
[addItemTextField typeText:@"Hello"];
//回車鍵
[app typeText:@"\r"];
//獲取表格中的Cell對象,每一個(gè)表格單元都是一個(gè)Cell對象
XCUIElementQuery *cells = [tables childrenMatchingType:XCUIElementTypeCell];
//獲取第二個(gè)單元昆庇,跳過了輸入框所在的行末贾,XCUIElement對象
XCUIElement *cell = [cells elementAtIndex:1];
[cell swipeLeft];
[cell swipeRight];
//從第一個(gè)單元中找到文本框控件集合,XCUIElementQuery對象
XCUIElementQuery *textFields = [cell childrenMatchingType:XCUIElementTypeTextField];
//XCUIElementQuery *textFields = [cell descendantsMatchingType:XCUIElementTypeTextField];
//獲取第一個(gè)文本框控件整吆,XCUIElement對象
XCUIElement *textField = [textFields elementAtIndex:0];
if ([textField exists]) {
//左滑然后右滑
[textField swipeLeft];
[textField swipeRight];
}
//長按5.5秒
[textField pressForDuration:5.5];
//打印debug的描述信息拱撵,也就是case執(zhí)行過程中的查找過程
NSLog(@" %@ ",[textField debugDescription]);
}
第三 XCUIElementQuery
定位元素的對象,可以理解為存放控件的容器,具有容器的特性.但是無法像數(shù)組和字典一樣打印里面的對象.
方法
elementAtIndex
獲得傳入的索引值所在的元素,返回XCUIElement對象。只能從當(dāng)前對象的查找表蝙。更深層次的元素不在查找范圍內(nèi)
elementMatchingPredicate
根據(jù)NSPredicate定義的匹配條件查找元素拴测。返回XCUIElement對象。只能從當(dāng)前對象中查找府蛇。更深層次的元素不在查找范圍內(nèi)
elementMatchingType
根據(jù)元素類型(XCUIElementType)和id號來匹配查找元素集索。返回XCUIElement對象。只能從當(dāng)前對象中查找汇跨。更深層次的元素不在查找范圍內(nèi)
descendantsMatchingType
傳入XCUIElementType作為匹配條件抄谐,得到匹配的XCUIElementQuery對象,查找對象為當(dāng)前控件的子子孫孫控件扰法。返回XCUIElementQuery對象
childrenMatchingType
傳入XCUIElementType作為匹配條件蛹含,得到匹配的XCUIElementQuery對象,查找對象為當(dāng)前控件的兒子控件塞颁。返回XCUIElementQuery對象
matchingPredicate
傳入NSPredicate作為過濾器浦箱,得到XCUIElementQuery對象。返回XCUIElementQuery對象
matchingType
傳入XCUIElementType和id號作為匹配條件祠锣,得到XCUIElementQuery酷窥。返回XCUIElementQuery對象
matchingIdentifier
傳入id號作為匹配條件,得到XCUIElementQuery伴网。返回XCUIElementQuery對象
containingPredicate
傳入NSPredicate過濾器作為匹配條件蓬推。從子節(jié)點(diǎn)中找到包含該條件的XCUIElementQuery對象
containingType
傳入XCUIElementType和id作為匹配條件。從子節(jié)點(diǎn)中找到包含該條件的XCUIElementQuery對象澡腾。
屬性
element
query用element表示形式沸伏,如果query中只有一個(gè)元素,可以講element當(dāng)成真正的element动分,執(zhí)行點(diǎn)擊等操作毅糟,從這一方面來講XCUIElementQuery其實(shí)也是一種XCUIElement對象,只是是用來存放0~N個(gè)XCUIElement的容器澜公。得到XCUIElement對象姆另。
count
query中找到的元素?cái)?shù)量,得到整數(shù)。
allElementsBoundByAccessibilityElement
query中根據(jù)accessibility element得到的元素?cái)?shù)組。得到XCUIElement數(shù)組
allElementsBoundByIndex
query中根據(jù)索引值得到的元素?cái)?shù)組迹辐。得到XCUIElement數(shù)組
debugDescription
調(diào)試信息
下標(biāo)
subscript (key: String) -> XCUIElement { get } 使得我們下面通過下標(biāo)定位成為了可能蝶防。返回XCUIElement對象
app.tables.staticTexts["Groceries"]
實(shí)例
使用element開頭的三個(gè)方法查找
func testXCUIElementQueryByElement() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let app = XCUIApplication()
//獲取所有textField的query對象
let query = app.windows.textFields
let element = query.element
//創(chuàng)建匹配器,匹配placeholderValue的值為Type in number的控件
let predicate = NSPredicate(format: "placeholderValue == %@", "Type in number")
let button = query.elementMatchingPredicate(predicate);
let button2 = query.elementAtIndex(0)
if(button.exists){
button.tap()
button.typeText("button")
}
if(button2.exists){
button2.tap()
button2.typeText("button2")
}
//創(chuàng)建匹配器明吩,匹配placeholderValue的值為Type in number且value值為Hello的控件
let predicate1 = NSPredicate(format: "value == %@ AND placeholderValue == %@", "button","Type in number")
let button3 = query.elementMatchingPredicate(predicate1);
if(button3.exists){
button3.tap()
button3.typeText("button3")
}
//根據(jù)elementMatchingType方法查找元素
let button4 = query.elementMatchingType(.TextField, identifier: "")
if(button4.exists){
button4.tap()
button4.typeText("button4")
}
}
第四 XCUIElementTypeQueryProvider
協(xié)議類间学,XCUIElement遵守的協(xié)議
變量
該協(xié)議中定義了76個(gè)變量,與XCUIElementType定義的枚舉元素相比少了3個(gè):Any,Unknown,Application.原因也很明顯贺喝,因?yàn)閄CUIApplication也遵循該協(xié)議,所以Application對象包含XCUIElementTypeQueryProvider定義的所有屬性宗兼,所以要過濾掉以上三個(gè)大于Application的類型躏鱼。
源碼
@property (readonly, copy) XCUIElementQuery *touchBars;
@property (readonly, copy) XCUIElementQuery *groups;
@property (readonly, copy) XCUIElementQuery *windows;
@property (readonly, copy) XCUIElementQuery *sheets;
@property (readonly, copy) XCUIElementQuery *drawers;
@property (readonly, copy) XCUIElementQuery *alerts;
@property (readonly, copy) XCUIElementQuery *dialogs;
@property (readonly, copy) XCUIElementQuery *buttons;
@property (readonly, copy) XCUIElementQuery *radioButtons;
@property (readonly, copy) XCUIElementQuery *radioGroups;
@property (readonly, copy) XCUIElementQuery *checkBoxes;
@property (readonly, copy) XCUIElementQuery *disclosureTriangles;
@property (readonly, copy) XCUIElementQuery *popUpButtons;
@property (readonly, copy) XCUIElementQuery *comboBoxes;
@property (readonly, copy) XCUIElementQuery *menuButtons;
@property (readonly, copy) XCUIElementQuery *toolbarButtons;
@property (readonly, copy) XCUIElementQuery *popovers;
@property (readonly, copy) XCUIElementQuery *keyboards;
@property (readonly, copy) XCUIElementQuery *keys;
@property (readonly, copy) XCUIElementQuery *navigationBars;
@property (readonly, copy) XCUIElementQuery *tabBars;
@property (readonly, copy) XCUIElementQuery *tabGroups;
@property (readonly, copy) XCUIElementQuery *toolbars;
@property (readonly, copy) XCUIElementQuery *statusBars;
@property (readonly, copy) XCUIElementQuery *tables;
@property (readonly, copy) XCUIElementQuery *tableRows;
@property (readonly, copy) XCUIElementQuery *tableColumns;
@property (readonly, copy) XCUIElementQuery *outlines;
@property (readonly, copy) XCUIElementQuery *outlineRows;
@property (readonly, copy) XCUIElementQuery *browsers;
@property (readonly, copy) XCUIElementQuery *collectionViews;
@property (readonly, copy) XCUIElementQuery *sliders;
@property (readonly, copy) XCUIElementQuery *pageIndicators;
@property (readonly, copy) XCUIElementQuery *progressIndicators;
@property (readonly, copy) XCUIElementQuery *activityIndicators;
@property (readonly, copy) XCUIElementQuery *segmentedControls;
@property (readonly, copy) XCUIElementQuery *pickers;
@property (readonly, copy) XCUIElementQuery *pickerWheels;
@property (readonly, copy) XCUIElementQuery *switches;
@property (readonly, copy) XCUIElementQuery *toggles;
@property (readonly, copy) XCUIElementQuery *links;
@property (readonly, copy) XCUIElementQuery *images;
@property (readonly, copy) XCUIElementQuery *icons;
@property (readonly, copy) XCUIElementQuery *searchFields;
@property (readonly, copy) XCUIElementQuery *scrollViews;
@property (readonly, copy) XCUIElementQuery *scrollBars;
@property (readonly, copy) XCUIElementQuery *staticTexts;
@property (readonly, copy) XCUIElementQuery *textFields;
@property (readonly, copy) XCUIElementQuery *secureTextFields;
@property (readonly, copy) XCUIElementQuery *datePickers;
@property (readonly, copy) XCUIElementQuery *textViews;
@property (readonly, copy) XCUIElementQuery *menus;
@property (readonly, copy) XCUIElementQuery *menuItems;
@property (readonly, copy) XCUIElementQuery *menuBars;
@property (readonly, copy) XCUIElementQuery *menuBarItems;
@property (readonly, copy) XCUIElementQuery *maps;
@property (readonly, copy) XCUIElementQuery *webViews;
@property (readonly, copy) XCUIElementQuery *steppers;
@property (readonly, copy) XCUIElementQuery *incrementArrows;
@property (readonly, copy) XCUIElementQuery *decrementArrows;
@property (readonly, copy) XCUIElementQuery *tabs;
@property (readonly, copy) XCUIElementQuery *timelines;
@property (readonly, copy) XCUIElementQuery *ratingIndicators;
@property (readonly, copy) XCUIElementQuery *valueIndicators;
@property (readonly, copy) XCUIElementQuery *splitGroups;
@property (readonly, copy) XCUIElementQuery *splitters;
@property (readonly, copy) XCUIElementQuery *relevanceIndicators;
@property (readonly, copy) XCUIElementQuery *colorWells;
@property (readonly, copy) XCUIElementQuery *helpTags;
@property (readonly, copy) XCUIElementQuery *mattes;
@property (readonly, copy) XCUIElementQuery *dockItems;
@property (readonly, copy) XCUIElementQuery *rulers;
@property (readonly, copy) XCUIElementQuery *rulerMarkers;
@property (readonly, copy) XCUIElementQuery *grids;
@property (readonly, copy) XCUIElementQuery *levelIndicators;
@property (readonly, copy) XCUIElementQuery *cells;
@property (readonly, copy) XCUIElementQuery *layoutAreas;
@property (readonly, copy) XCUIElementQuery *layoutItems;
@property (readonly, copy) XCUIElementQuery *handles;
@property (readonly, copy) XCUIElementQuery *otherElements;
第五 XCUIElementAttributes
協(xié)議類,XCUIElement遵守的協(xié)議
屬性
identifier
字符串類型
frame
控件的矩形區(qū)域
value
title
標(biāo)題殷绍,String類型
label
標(biāo)簽值染苛,String類型
elementType
控件類型
enabled
是否可見,BOOL類型
horizontalSizeClass
verticalSizeClass
實(shí)例
Swift
func testXCUIElementAttributes(){
let app = XCUIApplication()
//id
let id = app.identifier
print(id)
//frame
let frame = app.frame
//value
let value = app.value
//title
let title = app.title
//label
let label = app.label
//elementType
let elementType = app.elementType
//enabled
let enabled = app.enabled
//horizontalSizeClass
let horizontalSizeClass = app.horizontalSizeClass
//verticalSizeClass
let verticalSizeClass = app.verticalSizeClass
}
http://blog.csdn.net/itfootball/article/details/46621615
第六 NSPredicate
該API 在UITesting中用到了,所以有必要學(xué)習(xí)一下
實(shí)例
func testXCUIElementQueryByElement() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let app = XCUIApplication()
//獲取所有textField的query對象
let query = app.windows.textFields
let element = query.element
//創(chuàng)建匹配器主到,匹配placeholderValue的值為Type in number的控件
let predicate = NSPredicate(format: "placeholderValue == %@", "Type in number")
let button = query.elementMatchingPredicate(predicate);
let button2 = query.elementAtIndex(0)
if(button.exists){
button.tap()
button.typeText("button")
}
if(button2.exists){
button2.tap()
button2.typeText("button2")
}
//創(chuàng)建匹配器茶行,匹配placeholderValue的值為Type in number且value值為Hello的控件
let predicate1 = NSPredicate(format: "value == %@ AND placeholderValue == %@", "button","Type in number")
let button3 = query.elementMatchingPredicate(predicate1);
if(button3.exists){
button3.tap()
button3.typeText("button3")
}
//根據(jù)elementMatchingType方法查找元素
let button4 = query.elementMatchingType(.TextField, identifier: "")
if(button4.exists){
button4.tap()
button4.typeText("button4")
}
}
以上就是XCTest的API只要你學(xué)會(huì)這些就可以創(chuàng)建你想要的腳本了.
總結(jié)和反思
雖然上面的內(nèi)容并不多,我用了這么多時(shí)間主要是希望可以用這個(gè)實(shí)現(xiàn)一套人性的框架來測試app.但是至我發(fā)稿我都沒有實(shí)現(xiàn)了一個(gè)完善的框架.該框架我的想法是我們把所以的控件都放入數(shù)組.然后在for循環(huán)實(shí)現(xiàn)每個(gè)按鈕的點(diǎn)擊.但是我只能做到在獲取到這些控件之后,都點(diǎn)擊了然后重啟app就是使用launch()方法,在點(diǎn)擊下個(gè)控件.但是耗時(shí)太久.不是個(gè)好方法,而且我for循環(huán)獲取的所有控件并不代表我獲取了這個(gè)頁面的所有控件,很多復(fù)雜的控件是相互嵌套的.造成控件獲取不全,而在實(shí)現(xiàn)點(diǎn)擊的時(shí)候會(huì)造成各種莫名的奔潰.只要是我們腳本的奔潰,我們又只能重來運(yùn)行腳本.而獲取一個(gè)頁面的所有控件就耗時(shí)5,6分鐘了.效率很低.造成該框架一直無法實(shí)現(xiàn).
再次我希望該文章對你們有用的人可以往這方面想想,給我一些思路!謝謝