启示
iOS的自动化测试相对安卓的成熟度底很多.而自动化测试的目的是减轻人工测试的压力.刚刚看一文章提到“没有两片相同的叶子”,也没有两次相同的手工测试.测试,给人的一个错觉就是重复性,包括有些公司在招测试人员的时候会有一条叫“能够胜任重复性工作”。但试问,谁会手工运行N次相同的测试呢?一个测试人员,他在执行什么测试不重要,他为什么要执行这个测试——背后的分析思考才重要。自动化的测试,能够替代的是相同的执行,却无法替代人的分析与思考。而软件产品在研发迭代的过程,是团队不断地把新的分析思考注入其中的过程。测试工作面对的是始终在不断变化的测试需求。所以测试的分析思考,包括手工和自动化的测试也需要不断地迭代改善。
我16年底接到老大的要求让我探索自动化测试.下面就是我研究了一个月的成果希望可以帮到刚刚踏入自动化测试的童靴.
对于iOS的自动化测试有很多种.其他的很多种都是需要比较多的时间来学习写脚本的能力.而XCTest是Xcode自带.本身的脚本学法可以用OC或者是Swift.个人推荐使用Xcode8的Swift语句.
实现
自从Xcode7的开始XCTest的UI测试就开始出现.到Xcode8去掉UIAutoumator了.那说明XCTest在不久的将来会提供更多的接口或者被优化.(-_-当然也有可能会出更好的新插件).
上图
上面的可以进行单元测试和接口测试.而下面的则进行UI测试.今天只对UI测试做详细的介绍.对于单元测试各位上网查找一下对着用就行了.
在我们从xcode7开始都是默认创建
如果说我们的工程吧这两个文件删了或者一开始没有加进去我们可以
得到了我们所要的页面了
那我们要怎么去用其实很简单.我们就点下面那个红点.那个就是录制功能.进入录制的时候,我们点击我们的模拟器或者真机,它就会把我们的操作用代码记录下来.
如上图的第四块就是我们录制生成的代码.那我们又怎么运行这些代码呢.你可以使用快捷键CMD+U 或者你可以点击第一块的的蓝色方块.然后可以在第二块或者第三块的绿色打钩的地方点击.鼠标指向这个地方会一个灰色的播放按钮.就可以运行脚本了.
XCTest的API
现在就是来学习XCTest的API.
第一 XCUIApplication
XCTest新加的类,用于做UI测试,代表被测应用,父类为XCUIElement
主要的两个方法
launch
启动应用。如果目标应用已运行,首先终止应用,然后再次启动应用。
terminate
关闭应用。
两个属性
launchArguments
数组对象,保存启动参数。
launchEnvironment
字典对象,保存启动环境变量
实例
Swift版本
func testXCUIApplicationAPI(){
let app = XCUIApplication()
//关闭应用
app.terminate()
//启动应用(如果应用已启动,该方法会先关闭应用,再启动应用)
app.launch()
//获取启动参数
let args = app.launchArguments;
for arg in args {
print(arg);
}
//获取启动环境变量
let envs = app.launchEnvironment;
for env in envs {
print(env);
}
}
OC版本
- (void)testXCUIApplicationAPI {
XCUIApplication *app = [[XCUIApplication alloc] init];
//关闭应用
[app terminate];
//重新启动引用
[app launch];
//启动参数
NSArray *args = [app launchArguments];
for(int i=0;i<[args count];i++){
NSLog(@"arg : %@",[args objectAtIndex:i]);
}
//启动环境
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
从该控件的后代控件中找到符合指定类型的控件(子子孙孙都认),需要传入XCUIElementType类型的参数,返回XCUIElementType类型的对象。
childrenMatchingType
只从该控件的孩子节点中找到符合指定类型的控件(只认儿子),需要传入XCUIElementType类型的参数,返回XCUIElementType类型的对象。
属性
exists
判断控件对象是否存在。BOOL类型。
debugDescription
保存某控件的debug信息,这些信息只能用于case的调试,方便我们写case,不能作为测试的数据。NSString类型。
扩展XCUIElement
无论OC和Swift都是在XCUIElement的基础上扩展如下方法
tap
单击
doubleTap
双击
twoFingerTap
双指单击
pressForDuration(duration: NSTimeInterval)
长按(Swift写法),时间由传入的参数定义,单位为秒
pressForDuration(duration: NSTimeInterval, thenDragToElement otherElement: XCUIElement)
长按拖拽(Swift写法)。在控件上长按后,拖拽到另外一个控件。传入2个参数:长按时间和拖拽到目标控件。
swipeUp
控件上滑动。从下划到上
swipeDown
控件上滑动。从上滑到上
swipeLeft
控件上滑动。从右滑到左
swipeRight
控件上滑动。从左滑到右
typeText
输入字符。需要一个参数:NSString
实例
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()
//向上滑动
app.swipeUp()
//向下滑动
app.swipeDown()
//点击“Groceries”列进入子目录
app.tables.staticTexts["Groceries"].tap()
//获取当前界面的表格对象
let tables = app.tables
//定位到编辑框"Add Item"
let addItemTextField = tables.textFields["Add Item"]
//点击,焦点定位到编辑框
addItemTextField.tap()
//输入Hello
addItemTextField.typeText("Hello")
//回车键
app.typeText("\r")
//获取表格中的Cell对象,每一个表格单元都是一个Cell对象
let cells = tables.childrenMatchingType(.Cell)
//获取第二个单元,跳过了输入框所在的行,XCUIElement对象
let cell = cells.elementAtIndex(1)
cell.swipeLeft()
cell.swipeRight()
//从第一个单元中找到文本框控件集合,XCUIElementQuery对象
let textFields = cell.childrenMatchingType(.TextField)
//let textFields = cell.descendantsMatchingType(.TextField)
//获取第一个文本框控件,XCUIElement对象
let textField = textFields.elementAtIndex(0)
if textField.exists{
//左滑然后右滑
textField.swipeLeft()
textField.swipeRight()
}
//长按5.5秒
textField.pressForDuration(5.5)
//打印debug的描述信息,也就是case执行过程中的查找过程
print(textFields.debugDescription)
}
OC版本
-(void)testXCUIElementAPI {
XCUIApplication *app = [[XCUIApplication alloc] init];
//向上滑动
[app swipeUp];
//向下滑动
[app swipeDown];
//点击“Groceries”列进入子目录
[app.tables.staticTexts[@"Groceries"] tap];
//获取当前界面的表格对象
XCUIElementQuery *tables = app.tables;
//定位到编辑框"Add Item"
XCUIElement *addItemTextField = tables.textFields[@"Add Item"];
//点击,焦点定位到编辑框
[addItemTextField tap];
//输入Hello
[addItemTextField typeText:@"Hello"];
//回车键
[app typeText:@"\r"];
//获取表格中的Cell对象,每一个表格单元都是一个Cell对象
XCUIElementQuery *cells = [tables childrenMatchingType:XCUIElementTypeCell];
//获取第二个单元,跳过了输入框所在的行,XCUIElement对象
XCUIElement *cell = [cells elementAtIndex:1];
[cell swipeLeft];
[cell swipeRight];
//从第一个单元中找到文本框控件集合,XCUIElementQuery对象
XCUIElementQuery *textFields = [cell childrenMatchingType:XCUIElementTypeTextField];
//XCUIElementQuery *textFields = [cell descendantsMatchingType:XCUIElementTypeTextField];
//获取第一个文本框控件,XCUIElement对象
XCUIElement *textField = [textFields elementAtIndex:0];
if ([textField exists]) {
//左滑然后右滑
[textField swipeLeft];
[textField swipeRight];
}
//长按5.5秒
[textField pressForDuration:5.5];
//打印debug的描述信息,也就是case执行过程中的查找过程
NSLog(@" %@ ",[textField debugDescription]);
}
第三 XCUIElementQuery
定位元素的对象,可以理解为存放控件的容器,具有容器的特性.但是无法像数组和字典一样打印里面的对象.
方法
elementAtIndex
获得传入的索引值所在的元素,返回XCUIElement对象。只能从当前对象的查找。更深层次的元素不在查找范围内
elementMatchingPredicate
根据NSPredicate定义的匹配条件查找元素。返回XCUIElement对象。只能从当前对象中查找。更深层次的元素不在查找范围内
elementMatchingType
根据元素类型(XCUIElementType)和id号来匹配查找元素。返回XCUIElement对象。只能从当前对象中查找。更深层次的元素不在查找范围内
descendantsMatchingType
传入XCUIElementType作为匹配条件,得到匹配的XCUIElementQuery对象,查找对象为当前控件的子子孙孙控件。返回XCUIElementQuery对象
childrenMatchingType
传入XCUIElementType作为匹配条件,得到匹配的XCUIElementQuery对象,查找对象为当前控件的儿子控件。返回XCUIElementQuery对象
matchingPredicate
传入NSPredicate作为过滤器,得到XCUIElementQuery对象。返回XCUIElementQuery对象
matchingType
传入XCUIElementType和id号作为匹配条件,得到XCUIElementQuery。返回XCUIElementQuery对象
matchingIdentifier
传入id号作为匹配条件,得到XCUIElementQuery。返回XCUIElementQuery对象
containingPredicate
传入NSPredicate过滤器作为匹配条件。从子节点中找到包含该条件的XCUIElementQuery对象
containingType
传入XCUIElementType和id作为匹配条件。从子节点中找到包含该条件的XCUIElementQuery对象。
属性
element
query用element表示形式,如果query中只有一个元素,可以讲element当成真正的element,执行点击等操作,从这一方面来讲XCUIElementQuery其实也是一种XCUIElement对象,只是是用来存放0~N个XCUIElement的容器。得到XCUIElement对象。
count
query中找到的元素数量,得到整数。
allElementsBoundByAccessibilityElement
query中根据accessibility element得到的元素数组。得到XCUIElement数组
allElementsBoundByIndex
query中根据索引值得到的元素数组。得到XCUIElement数组
debugDescription
调试信息
下标
subscript (key: String) -> XCUIElement { get } 使得我们下面通过下标定位成为了可能。返回XCUIElement对象
app.tables.staticTexts["Groceries"]
实例
使用element开头的三个方法查找
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
//创建匹配器,匹配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")
}
//创建匹配器,匹配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")
}
//根据elementMatchingType方法查找元素
let button4 = query.elementMatchingType(.TextField, identifier: "")
if(button4.exists){
button4.tap()
button4.typeText("button4")
}
}
第四 XCUIElementTypeQueryProvider
协议类,XCUIElement遵守的协议
变量
该协议中定义了76个变量,与XCUIElementType定义的枚举元素相比少了3个:Any,Unknown,Application.原因也很明显,因为XCUIApplication也遵循该协议,所以Application对象包含XCUIElementTypeQueryProvider定义的所有属性,所以要过滤掉以上三个大于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
协议类,XCUIElement遵守的协议
属性
identifier
字符串类型
frame
控件的矩形区域
value
title
标题,String类型
label
标签值,String类型
elementType
控件类型
enabled
是否可见,BOOL类型
horizontalSizeClass
verticalSizeClass
实例
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中用到了,所以有必要学习一下
实例
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
//创建匹配器,匹配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")
}
//创建匹配器,匹配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")
}
//根据elementMatchingType方法查找元素
let button4 = query.elementMatchingType(.TextField, identifier: "")
if(button4.exists){
button4.tap()
button4.typeText("button4")
}
}
以上就是XCTest的API只要你学会这些就可以创建你想要的脚本了.
总结和反思
虽然上面的内容并不多,我用了这么多时间主要是希望可以用这个实现一套人性的框架来测试app.但是至我发稿我都没有实现了一个完善的框架.该框架我的想法是我们把所以的控件都放入数组.然后在for循环实现每个按钮的点击.但是我只能做到在获取到这些控件之后,都点击了然后重启app就是使用launch()方法,在点击下个控件.但是耗时太久.不是个好方法,而且我for循环获取的所有控件并不代表我获取了这个页面的所有控件,很多复杂的控件是相互嵌套的.造成控件获取不全,而在实现点击的时候会造成各种莫名的奔溃.只要是我们脚本的奔溃,我们又只能重来运行脚本.而获取一个页面的所有控件就耗时5,6分钟了.效率很低.造成该框架一直无法实现.
再次我希望该文章对你们有用的人可以往这方面想想,给我一些思路!谢谢