美文网首页ios 学习
iOS自动化测试 ==>针对XCODE自带的XCTest

iOS自动化测试 ==>针对XCODE自带的XCTest

作者: Y_Swordsman | 来源:发表于2017-02-16 15:15 被阅读2478次

    启示

       iOS的自动化测试相对安卓的成熟度底很多.而自动化测试的目的是减轻人工测试的压力.刚刚看一文章提到“没有两片相同的叶子”,也没有两次相同的手工测试.测试,给人的一个错觉就是重复性,包括有些公司在招测试人员的时候会有一条叫“能够胜任重复性工作”。但试问,谁会手工运行N次相同的测试呢?一个测试人员,他在执行什么测试不重要,他为什么要执行这个测试——背后的分析思考才重要。自动化的测试,能够替代的是相同的执行,却无法替代人的分析与思考。而软件产品在研发迭代的过程,是团队不断地把新的分析思考注入其中的过程。测试工作面对的是始终在不断变化的测试需求。所以测试的分析思考,包括手工和自动化的测试也需要不断地迭代改善。

    我16年底接到老大的要求让我探索自动化测试.下面就是我研究了一个月的成果希望可以帮到刚刚踏入自动化测试的童靴.

    对于iOS的自动化测试有很多种.其他的很多种都是需要比较多的时间来学习写脚本的能力.而XCTest是Xcode自带.本身的脚本学法可以用OC或者是Swift.个人推荐使用Xcode8的Swift语句.

    实现

    自从Xcode7的开始XCTest的UI测试就开始出现.到Xcode8去掉UIAutoumator了.那说明XCTest在不久的将来会提供更多的接口或者被优化.(-_-当然也有可能会出更好的新插件).

    上图

    单元测试和UI测试

    上面的可以进行单元测试和接口测试.而下面的则进行UI测试.今天只对UI测试做详细的介绍.对于单元测试各位上网查找一下对着用就行了.

    在我们从xcode7开始都是默认创建

    创建工程

    如果说我们的工程吧这两个文件删了或者一开始没有加进去我们可以

    添加UITest (1) 添加UITest (2)

    得到了我们所要的页面了

    UITest的代码

    那我们要怎么去用其实很简单.我们就点下面那个红点.那个就是录制功能.进入录制的时候,我们点击我们的模拟器或者真机,它就会把我们的操作用代码记录下来.

    生成代码

    如上图的第四块就是我们录制生成的代码.那我们又怎么运行这些代码呢.你可以使用快捷键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分钟了.效率很低.造成该框架一直无法实现.

    再次我希望该文章对你们有用的人可以往这方面想想,给我一些思路!谢谢

    相关文章

      网友评论

      • 囚砚:不能持续测试是很大的问题,运行一次就完事,停止了是吧??
        Y_Swordsman:可以吧:joy: 是让App重复启动区测试的。这种循环测试的耗时太大了就是
      • 48c6ed7446a4:现在在调研自动化测试,正需要整理出篇文档,很有用,感谢楼主。顺便说一句,根据我现在对自动化测试浅显的了解,感觉想比较好的对app进行深入的自动化测试实现起来比较困难,还在继续探索,希望能有所收获。
      • 国家洗霾兄弟:请问有没有去了解过KIF呢?
        Y_Swordsman:@大边沿的草帽 pressForDuration(duration: NSTimeInterval) 一般用这个
        c75a7bcd2d75:请问如何在点击一次操作后停留几秒再进行下一步操作
        Y_Swordsman:@国家洗霾兄弟 不好意思,没有。

      本文标题:iOS自动化测试 ==>针对XCODE自带的XCTest

      本文链接:https://www.haomeiwen.com/subject/uxhwittx.html