20、iOS UI 测试全攻略

iOS UI 测试全攻略

1. 引言

在最新的 Xcode 中,Apple 为 UI 测试添加了一个相当出色的框架。UI 测试不仅有趣,还与无障碍访问密切相关。在模拟器上调试支持无障碍访问的应用时,可使用 Xcode 自带的无障碍访问检查器,通过右键点击 Dock 中的 Xcode 图标,选择“Open Developer Tool”里的“Accessibility Inspector”来找到它,该工具能让你获取屏幕上元素的无障碍访问属性信息。接下来,我们将介绍如何编写 UI 测试并评估结果,会用到 Xcode 的自动化 UI 测试,也会手动编写一些测试代码。

2. 为项目准备 UI 测试

2.1 问题描述

无论是已有应用还是要创建新应用,都希望为应用构建一些 UI 测试功能,以便开始编写 UI 测试。

2.2 解决方案

  • 新创建项目 :在设置项目属性时,启用“Include UI Tests”选项。
  • 已有项目 :创建一个新的目标,在模板屏幕中,选择 iOS 下的“Test”,然后选择“Cocoa Touch UI Testing Bundle”。在后续屏幕中,选择要创建 UI 测试目标的目标,也可在之后从 UI 测试目标的属性中更改。

2.3 操作步骤

项目情况 操作步骤
新创建项目 1. 设置项目属性
2. 启用“Include UI Tests”选项
已有项目 1. 创建新目标
2. 选择 iOS 下的“Test”
3. 选择“Cocoa Touch UI Testing Bundle”
4. 选择要创建 UI 测试目标的目标(可后续更改)
graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([开始]):::startend --> B{项目情况}:::process
    B -->|新创建项目| C(设置项目属性):::process
    C --> D(启用“Include UI Tests”选项):::process
    B -->|已有项目| E(创建新目标):::process
    E --> F(选择 iOS 下的“Test”):::process
    F --> G(选择“Cocoa Touch UI Testing Bundle”):::process
    G --> H(选择要创建 UI 测试目标的目标):::process
    H --> I(可后续更改目标):::process
    D --> J([结束]):::startend
    I --> J

3. 自动化 UI 测试脚本

3.1 问题描述

希望 Xcode 生成大部分(甚至全部)UI 测试代码,同时也能在 Swift 中编写更多测试代码。

3.2 解决方案

在 UI 测试目标的代码中,使用 Xcode 的新录制按钮(位于 Xcode 窗口调试器部分左上角的红色圆圈处)。

3.3 操作步骤

  1. 假设 UI 中有一个文本框、一个按钮和一个标签。在 Interface Builder 的 Identity inspector 中,将文本框的无障碍访问标签设置为“Full Name”,按钮的标签设置为“Capitalize”,标签的设置为“Capitalized String”。
  2. 将文本框和标签连接到代码中,命名为“lbl”和“txtField”,并将按钮的动作连接到代码中的“capitalize()”方法:
@IBAction func capitalize() {
    guard let txt = txtField.text, txt.characters.count > 0 else{
        return
    }
    lbl.text = txt.uppercased()
    lbl.accessibilityValue = lbl.text
}
  1. 进入 UI 测试的主 Swift 文件,在“testExample()”方法中,按下录制按钮。
  2. 像用户一样操作,点击文本框并输入文本,如“Hello, World!”,然后点击“Capitalize”按钮。Xcode 将生成类似以下的测试代码:
let app = XCUIApplication()
let fullNameTextField = app.textFields["Full Name"]
fullNameTextField.tap()
fullNameTextField.typeText(enteredString)
app.buttons["Capitalize"].tap()
  1. 要确保标签中的大写文本正确大写,需手动编写代码:
let lbl = app.staticTexts["Capitalized String"]
  1. 按下测试方法旁边的小播放按钮,若一切正常,测试将成功。

4. 测试文本框、按钮和标签

4.1 问题描述

想要创建用于测试 UITextField、UIButton 和 UILabel 实例的 UI 测试。

4.2 解决方案

这些 UI 组件都是 XCUIElement 类型的实例,可使用以下属性进行 UI 测试:
- exists
- title
- label
- enabled
- frame
- debugDescription
- descendantsMatchingType( :)
- childrenMatchingType(
:)

4.3 操作步骤

  1. 假设界面中有一个按钮和一个标签,按下按钮时隐藏标签。为按钮和标签设置无障碍访问标签分别为“Button”和“Label”。
  2. 打开 UI 测试的录制部分,按下按钮,将得到类似以下的代码:
let app = XCUIApplication()
app.buttons["Button"].tap()
  1. 查找标签:
let lbl = app.staticTexts["Label"]
  1. 检查标签是否在屏幕上:
XCTAssert(lbl.exists == false)
  1. 也可读取文本框的值,使用调试器通过“po”命令检查文本框元素的 value 属性,使用 XCUIApplication() 实例化的 app 的 textFields 属性查找当前屏幕上的所有文本框。例如,查找具有特定无障碍访问标签的文本框:
let app = XCUIApplication()
let txtField = app.textFields["MyTextField"]
XCTAssert(txtField.exists)
XCTAssert(txtField.value != nil)
let txt = txtField.value as! String
XCTAssert(txt.characters.count > 0)

5. 查找 UI 组件

5.1 问题描述

希望能够使用简单到复杂的查询找到 UI 组件。

5.2 解决方案

构建 XCUIElementQuery 类型的查询,将这些查询链接在一起以创建更复杂的查询,从而找到 UI 元素。XCUIElement 类符合 XCUIElementTypeQueryProvider 协议,该协议包含大量关于 UI 元素的属性。

5.3 操作步骤

以下是查找 UI 元素的推荐步骤:
1. 使用 XCUIApplication() 实例化应用:

let app = XCUIApplication()
  1. 引用应用对象的 windows 属性,以获取应用中所有窗口的查询对象:
let windowsQuery = app.windows
  1. 使用 childrenMatchingType(_:) 方法在查询中查找子元素。例如,假设视图层次结构中有一个视图控制器,其视图中包含另一个视图,该视图中又包含一个按钮,查找该按钮并点击的代码如下:
let view = app.windows.children(matching: .other)
let innerView = view.children(matching: .other)
let btn = innerView.children(matching: .button).element(boundBy: 0)
XCTAssert(btn.exists)
btn.tap()

也可以使用 descendantsMatchingType(_:) 方法以更直接和紧凑的方式编写代码:

let app = XCUIApplication()
let btn = app.windows.children(matching: .other)
  .descendants(matching: .button).element(boundBy: 0)
XCTAssert(btn.exists)
btn.tap()

还可以使用 NSPredicate 类型的谓词来查找元素。例如,查找具有特定标题的按钮:

let app = XCUIApplication()
let btns = app.buttons.matching(
  NSPredicate(format: "title like[c] 'Button'"))
XCTAssert(btns.count >= 1)
let btn = btns.element(boundBy: 0)
XCTAssert(btn.exists)

5.4 流程图

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([开始]):::startend --> B(实例化应用):::process
    B --> C(获取窗口查询对象):::process
    C --> D{选择查询方式}:::process
    D -->|childrenMatchingType| E(查找子元素):::process
    D -->|descendantsMatchingType| F(查找后代元素):::process
    D -->|NSPredicate| G(使用谓词查找元素):::process
    E --> H(检查元素是否存在):::process
    F --> H
    G --> H
    H --> I(执行操作,如点击):::process
    I --> J([结束]):::startend

6. 模拟长按 UI 元素

6.1 问题描述

希望能够使用 UI 测试模拟对 UI 元素的长按操作。

6.2 解决方案

使用 XCUIElement 的 pressForDuration(_:) 方法。

6.3 操作步骤

  1. 创建一个单视图应用,在视图加载时添加一个长按手势识别器:
override func viewDidLoad() {
    super.viewDidLoad()

    view.isAccessibilityElement = true

    let gr = UILongPressGestureRecognizer(target: self,
        action: #selector(ViewController.handleLongPress))

    gr.minimumPressDuration = 5

    view.addGestureRecognizer(gr)
}
  1. 实现长按手势识别器的处理方法,显示一个警报控制器,要求用户输入姓名,并将输入的值设置为视图的无障碍访问值:
func handleLongPress() {
    let c = UIAlertController(title: "Name", message: "What is your name?",
        preferredStyle: .alert)

    c.addAction(UIAlertAction(title: "Cancel", style: .destructive,
        handler: nil))

    c.addAction(UIAlertAction(title: "Save", style: .destructive){
        action in

            guard let fields = c.textFields, fields.count == 1 else{
                return
            }

            let txtField = fields[0]
            guard let txt = txtField.text, txt.characters.count > 0 else{
                return
            }

            self.view.accessibilityValue = txt

        })

    c.addTextField {txt in
        txt.placeholder = "Foo Bar"
    }

    present(c, animated: true, completion: nil)
}
  1. 在 UI 测试代码中进行以下操作:
let app = XCUIApplication()
let view = app.windows.children(matching: .other).element(boundBy: 0)
view.press(forDuration: 5)
XCTAssert(app.alerts.count > 0)
let text = "Foo Bar"
app.typeText(text)
let alert = app.alerts.element(boundBy: 0)
let saveBtn = alert.descendants(matching: .button).matching(
    NSPredicate(format: "title like[c] 'Save'")).element(boundBy: 0)
saveBtn.tap()
XCTAssert(view.value as! String == text)

7. 在文本框中输入内容

7.1 问题描述

希望为包含文本框的应用编写 UI 测试,能够激活文本框、输入文本、停用文本框,并对结果进行测试。

7.2 解决方案

按照以下步骤操作:
1. 使用应用的 textFields 属性或其他查找方法找到文本框。
2. 调用文本框的 tap() 方法激活它。
3. 调用文本框的 typeText( :) 方法输入所需文本。
4. 调用应用的 typeText(
:) 方法,传入 XCUIKeyboardKeyReturn 作为参数,模拟按下键盘上的 Enter 键。也可以使用其他 XCUIKeyboardKey 常量值,如 XCUIKeyboardKeySpace 或 XCUIKeyboardKeyCommand。
5. 完成输入后,将文本框元素的 value 属性读取为 String 类型,并进行测试。

7.3 操作步骤

  1. 创建一个单视图应用,放置一个文本框,将其辅助标签设置为“myText”。
  2. 将文本框的委托设置为视图控制器,并使视图控制器符合 UITextFieldDelegate 协议,实现 textFieldShouldReturn(_:) 方法,以便按下键盘上的返回按钮时隐藏键盘:
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}
  1. 在 UI 测试中编写如下代码:
let app = XCUIApplication()
let txtField = app.textFields["myText"]
txtField.tap()
txtField.typeText("Hello, World!")
app.typeText(XCUIKeyboardKeyReturn)
let text = txtField.value as! String
// 进行测试,例如
XCTAssert(text == "Hello, World!")

7.4 表格总结

步骤 操作 代码示例
1 找到文本框 let txtField = app.textFields["myText"]
2 激活文本框 txtField.tap()
3 输入文本 txtField.typeText("Hello, World!")
4 模拟按下 Enter 键 app.typeText(XCUIKeyboardKeyReturn)
5 读取文本框的值并测试 let text = txtField.value as! String; XCTAssert(text == "Hello, World!")
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值