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 操作步骤
- 假设 UI 中有一个文本框、一个按钮和一个标签。在 Interface Builder 的 Identity inspector 中,将文本框的无障碍访问标签设置为“Full Name”,按钮的标签设置为“Capitalize”,标签的设置为“Capitalized String”。
- 将文本框和标签连接到代码中,命名为“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
}
- 进入 UI 测试的主 Swift 文件,在“testExample()”方法中,按下录制按钮。
- 像用户一样操作,点击文本框并输入文本,如“Hello, World!”,然后点击“Capitalize”按钮。Xcode 将生成类似以下的测试代码:
let app = XCUIApplication()
let fullNameTextField = app.textFields["Full Name"]
fullNameTextField.tap()
fullNameTextField.typeText(enteredString)
app.buttons["Capitalize"].tap()
- 要确保标签中的大写文本正确大写,需手动编写代码:
let lbl = app.staticTexts["Capitalized String"]
- 按下测试方法旁边的小播放按钮,若一切正常,测试将成功。
4. 测试文本框、按钮和标签
4.1 问题描述
想要创建用于测试 UITextField、UIButton 和 UILabel 实例的 UI 测试。
4.2 解决方案
这些 UI 组件都是 XCUIElement 类型的实例,可使用以下属性进行 UI 测试:
- exists
- title
- label
- enabled
- frame
- debugDescription
- descendantsMatchingType(
:)
- childrenMatchingType(
:)
4.3 操作步骤
- 假设界面中有一个按钮和一个标签,按下按钮时隐藏标签。为按钮和标签设置无障碍访问标签分别为“Button”和“Label”。
- 打开 UI 测试的录制部分,按下按钮,将得到类似以下的代码:
let app = XCUIApplication()
app.buttons["Button"].tap()
- 查找标签:
let lbl = app.staticTexts["Label"]
- 检查标签是否在屏幕上:
XCTAssert(lbl.exists == false)
- 也可读取文本框的值,使用调试器通过“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()
- 引用应用对象的 windows 属性,以获取应用中所有窗口的查询对象:
let windowsQuery = app.windows
- 使用 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 操作步骤
- 创建一个单视图应用,在视图加载时添加一个长按手势识别器:
override func viewDidLoad() {
super.viewDidLoad()
view.isAccessibilityElement = true
let gr = UILongPressGestureRecognizer(target: self,
action: #selector(ViewController.handleLongPress))
gr.minimumPressDuration = 5
view.addGestureRecognizer(gr)
}
- 实现长按手势识别器的处理方法,显示一个警报控制器,要求用户输入姓名,并将输入的值设置为视图的无障碍访问值:
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)
}
- 在 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 操作步骤
- 创建一个单视图应用,放置一个文本框,将其辅助标签设置为“myText”。
- 将文本框的委托设置为视图控制器,并使视图控制器符合 UITextFieldDelegate 协议,实现 textFieldShouldReturn(_:) 方法,以便按下键盘上的返回按钮时隐藏键盘:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
- 在 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!")
|
超级会员免费看
27

被折叠的 条评论
为什么被折叠?



