Xcode7 UI自动化测试详解 带demo UITests

UI Tests是什么?

UI Tests是一个自动测试UI与交互的Testing组件

UI Tests有什么用?

它可以通过编写代码、或者是记录开发者的操作过程并代码化,来实现自动点击某个按钮、视图,或者自动输入文字等功能。

UI Tests的重要性

在实际的开发过程中,随着项目越做越大,功能越来越多,仅仅靠人工操作的方式来覆盖所有测试用例是非常困难的,尤其是加入新功能以后,旧的功能也要重新测试一遍,这导致了测试需要花非常多的时间来进行回归测试,这里产生了大量重复的工作,而这些重复的工作有些是可以自动完成的,这时候UI Tests就可以帮助解决这个问题了

使用方法

第一步:添加UI Tests

如果是新项目,则创建工程的时候可以直接勾选选项,如下图


创建工程
创建工程

如果是已有的项目,可以通过添加target的方式添加一个UI Tests,点击xcode的菜单,找到target栏


添加target
添加target

在Test选项中选择Cocoa Touch UI Testing Bundle


添加target_2
添加target_2

这时候test组件添加成功,它在项目中的位置如下图所示


目录结构
目录结构
第二步:创建测试代码

手动创建测试代码
打开测试文件,在testExample()方法中添加测试代码

这里写图片描述
这里写图片描述

如果不知道如何写测试代码,则可以参考自动生成的代码样式

自动生成测试步骤
选择测试文件后,点击录制按钮

这里写图片描述
这里写图片描述

这时候开始进行操作,它会记录你的操作步骤,并生成测试代码
下图就是在一些操作后自动生成的测试代码


这里写图片描述
这里写图片描述

这时候可以分析测试代码的语法,以便你自己手动修改或者手写测试代码

开始测试
点击testExample方法旁边的播放按钮,它就开始进行自动测试了,这时候你会看到app在自动操作

这里写图片描述
这里写图片描述

下面介绍一下测试元素的语法

XCUIApplication:
继承XCUIElement,这个类掌管应用程序的生命周期,里面包含两个主要方法
launch():
启动程序
terminate():
终止程序

**XCUIElement: **
继承NSObject,实现协议XCUIElementAttributes, XCUIElementTypeQueryProvider
可以表示系统的各种UI元素
exist:
可以让你判断当前的UI元素是否存在,如果对一个不存在的元素进行操作,会导致测试组件抛出异常并中断测试
descendantsMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某种类型的元素以及它的子类集合
childrenMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某种类型的元素集合,不包含它的子类

这两个方法的区别在于,你仅使用系统的UIButton时,用childrenMatchingType就可以了,如果你还希望查询自己定义的子Button,就要用descendantsMatchingType

另外UI元素还有一些交互方法
tap():
点击
doubleTap():
双击
pressForDuration(duration: NSTimeInterval):
长按一段时间,在你需要进行延时操作时,这个就派上用场了
swipeUp():
这个响应不了pan手势,暂时没发现能用在什么地方,也可能是beta版的bug,先不解释
typeText(text: String):
用于textField和textView输入文本时使用,使用前要确保文本框获得输入焦点,可以使用tap()函数使其获得焦点

XCUIElementAttributes协议
里面包含了UIAccessibility中的部分属性
如下图

这里写图片描述
这里写图片描述

可以方便你查看当前元素的特征,其中identifier属性可用于直接读取元素,不过该属性在UITextField中有bug,暂时不清楚原因

XCUIElementTypeQueryProvider协议
里面包含了系统中大部分UI控件的类型,可通过读属性的方式取得某种类型的UI集合
部分属性截图如下

这里写图片描述
这里写图片描述

创建Demo

首先创建一个登录页面


这里写图片描述
这里写图片描述

点击login按钮进行登录验证,点击clear按钮会清除文本
登录成功后可以去到个人信息页面

个人信息页面如下


这里写图片描述
这里写图片描述

点击modify按钮可以修改个人信息,点击Message按钮可以查看个人消息

最后是消息界面


这里写图片描述
这里写图片描述
登录页面的测试
  1. 输入一个错误的账号
  2. 验证结果
  3. 关闭警告窗
  4. 清除输入记录
  5. 输入一个正确的账号
  6. 验证结果
  7. 进入个人信息页面
    测试代码如下:
    func testLoginView() {
        let app = XCUIApplication()
       
        // 由于UITextField的id有问题,所以只能通过label的方式遍历元素来读取
        let nameField = self.getFieldWithLbl("nameField")
        if self.canOperateElement(nameField) {
            nameField!.tap()
            nameField!.typeText("xiaoming")
        }
       
        let psdField = self.getFieldWithLbl("psdField")
        if self.canOperateElement(psdField) {
            psdField!.tap()
            psdField!.typeText("1234321")
        }
       
        // 通过UIButton的预设id来读取对应的按钮
        let loginBtn = app.buttons["Login"]
        if self.canOperateElement(loginBtn) {
            loginBtn.tap()
        }
       
        // 开始一段延时,由于真实的登录是联网请求,所以不能直接获得结果,demo通过延时的方式来模拟联网请求
        let window = app.windows.elementAtIndex(0)
        if self.canOperateElement(window) {
            // 延时3秒, 3秒后如果登录成功,则自动进入信息页面,如果登录失败,则弹出警告窗
            window.pressForDuration(3)
        }
       
        // alert的id和labe都用不了,估计还是bug,所以只能通过数量判断
        if app.alerts.count > 0 {
            // 登录失败
            app.alerts.collectionViews.buttons["确定"].tap()
           
            let clear = app.buttons["Clear"]
            if self.canOperateElement(clear) {
                clear.tap()
               
                if self.canOperateElement(nameField) {
                    nameField!.tap()
                    nameField!.typeText("sun")
                }
               
                if self.canOperateElement(psdField) {
                    psdField!.tap()
                    psdField!.typeText("111111")
                }
               
                if self.canOperateElement(loginBtn) {
                    loginBtn.tap()
                }
                if self.canOperateElement(window) {
                    // 延时3秒, 3秒后如果登录成功,则自动进入信息页面,如果登录失败,则弹出警告窗
                    window.pressForDuration(3)
                }
                self.loginSuccess()
            }
        } else {
            // 登录成功
            self.loginSuccess()
        }
    }

这里有几个需要特别注意的点:

  1. 当你的元素不存在时,它仍然可能返回一个元素对象,但这时候不能对其进行操作
  2. 当你要点击的元素被键盘或者UIAlertView遮挡时,执行tap方法会抛异常
    详细实现可参照demo:
    https://github.com/sunljz/demo/tree/master/iOS9/UITestDemo
个人信息页测试
  1. 修改性别
  2. 修改年龄
  3. 修改心情
  4. 保存修改
    测试代码如下:
    func testInfo() {
        let app = XCUIApplication()
        let window = app.windows.elementAtIndex(0)
        if self.canOperateElement(window) {
            // 延时2秒, 加载数据需要时间
            window.pressForDuration(2)
        }
       
        let modifyBtn = app.buttons["modify"];
        modifyBtn.tap()
       
        let sexSwitch = app.switches["sex"]
        sexSwitch.tap()
       
        let incrementButton = app.buttons["Increment"]
        incrementButton.tap()
        incrementButton.tap()
        incrementButton.tap()
        app.buttons["Decrement"].tap()
       
        let textView = app.textViews["feeling"]
        textView.tap()
        app.keys["Delete"].tap()
        app.keys["Delete"].tap()
        textView.typeText(" abc ")
       
        // 点击空白区域
        let clearBtn = app.buttons["clearBtn"]
        clearBtn.tap()

        // 保存数据
        modifyBtn.tap()
        window.pressForDuration(2)
       
        let messageBtn = app.buttons["message"]
        messageBtn.tap();
       
        // 延时1秒, push view需要时间
        window.pressForDuration(1)
       
        self.testMessage()
    }

这里需要特别注意以下两点:

  1. textview获取焦点时无法选择焦点的位置
  2. tap事件的触发位置是view的中心,所以当view的中心被遮挡时,要考虑使用其他view来代替
个人消息界面测试
  1. 单元格的点击
    测试代码如下:
    func testMessage() {
        let app = XCUIApplication()
        let window = app.windows.elementAtIndex(0)
        if self.canOperateElement(window) {
            // 延时2秒, 加载数据需要时间
            window.pressForDuration(2)
        }
       
        let table = app.tables
        table.childrenMatchingType(.Cell).elementAtIndex(8).tap()
        table.childrenMatchingType(.Cell).elementAtIndex(1).tap()
       
    }

这里需要注意一点:

  1. 暂时无法获取到tableView的元素指针

总结

总的来说,UI Tests只能用于一些基础功能的测试,验证app的功能是否可以正常使用,是否存在崩溃问题。但它也有很多不足之处,编写测试用例的过程非常繁琐,自动生成的代码几乎无法运行,功能单一,很多用例无法覆盖,而且bug很多,大大地限制了UI Tests在实际开发中的应用。希望正式版出来的时候能够修复这些问题,并开放更多的功能。

demo地址:
https://github.com/sunljz/demo/tree/master/iOS9/UITestDemo

欢迎各位提出改进建议,感谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容