xcode7 UI 测试
原文地址:https://medium.com/@larcus94/ui-testing-with-xcode-7-221d16bad276
译者:孟祥月 博客:http://blog.youkuaiyun.com/mengxiangyue
大约两个两个星期之前,Apple发布了Xcode 7 的第一个beta版本,同时也带来了一些小巧的新特性,比如UI 测试。除了Swift,这个是最让我兴奋的一个特性。我迫不及待的想要去试试它。UI 测试并不是什么新的东西;KIF在2011年开始就已经做这方面的事情了。它是一个很强大的工具,但是它也有它得局限性:它运行缓慢、并不是100%可靠,并且它依赖于私有API。所以一个官方的解决方案听起来还是感觉很好的。
这个解决方案被集成在了Apple的单元测试框架XCTest中。使用它跟使用KIF很像。但是它使构建在一个新版本的UIAutomation之上,它是稳定的可信赖的。由于它被构集成进了Xcode,一些强大的特性,比如UI 记录(UI recording)和自动截屏,使它十分有用并且十分易用。
在接下来的章节中,我将讨论这两个框架最大的不同。
灵活性(Flexibility)
接下来,我开始为ImagePickerSheetController重写UI测试。ImagePickerSheetController是我的一个开源项目。它是一个自定义的在iMessage中我们选择一张图片的时候弹出来的action sheet(译者注:这个框架的功能可能描述不准确,因为在github上下载下来代码,我这里运行出错)。
我想重写的第一个测试是非常基本的。我只是想测试一下,当我点击cancel按钮的时候,这个controller能不能消失。
func testDismissal() {
let imageController = ImagePickerSheetController()
imageController.addAction(ImageAction(title: “Cancel”))
rootViewController.presentViewController(imageController, animated: true, completion: nil)
tester().tapViewWithAccessibilityLabel(“Cancel”)
tester().waitForAbsenceOfViewWithAccessibilityIdentifier(ID)
}
这个看起来十分简单,但是它不可能使用XCTest去实现。问题是Xcode UI测试不允许你访问真实的app。这个API紧紧是扮演了一个代理的角色。尝试直接获取一个view代理现在使用的XCUIElement,它只能表示这个view,并不能工作。
在WWDC的Developer Tools Labs,我被告知:XCTest的一个主要的目标就是使app更加的可测试化。我认为这个是有意义的,并且我开始重写我的一个叫做Crimson的app的测试。
在一系列基本的测试后,我开始测试‘accent view’(译者注:上图中的e和不同音调的View)。它能够显示一个字母的不同音调。我想要测试下面的两个方面:
- 当它出现得时候,第一个字母应该被选中,这里是‘e’
- accent view应该有一个颜色的key(原文:the accent view should have the same color as the key)
首先,我认为不可能为这些去写测试。在以前,你不能研究一个真正得view并且去检查它的属性(原文:As before, you can’t just investigate the actual view to check the properties.)。在一次查看文档的过程中,我偶然的返现了一个XCUIElement的一个叫做value的十分有用的属性。
/*! The raw value attribute of the element. Depending on the element, the actual type can vary. */
var value: AnyObject? { get }
它的作用就是返回UI element的accessibilityValue。这使在测试中获取属性成为了可能。所以现在的事情就是我需要在accent view上设置accessibilityValue。
class AccentView: UIView {
var selectedLetter: String {
didSet {
accessibilityValue = selectedLetter
}
}
}
直到我先要获取color之前,这个都工作得很好。由于accessibilityValue的类型是String,而不是Dictionary,它不可能获取到二级属性。另一反面,与accessibilityIdentifier相比,accessibilityValue是用户界面上的。设置accessibilityValue为一些难以理解的值,比如颜色,是非常不好的实践,并且完全违背了其目的。
写第二个测试也是不可能的。然而检查一个element的frame或者label是非常简单的,它几乎也不可能测试其他的东西。
性能(Performance)
为了测试两个框架的性能,我写了一个例子工程,包含3个测试。为了模拟合理的测试流程,每一个测试执行了20次,总共60次。
对于第一项测试,我按照文档实现了这个测试。对于XCTest,需要在每次测试的时候重启app。KIF不需要重启,这个使它在第一项测试上由于XCTest。
第二个测试,使用导航返回的方式重置app的状态代替了重启。
对于第三个测试,取消了所有的动画。
像我们在第一次基准测试中看到的一样,每次重启app使一组测试非常慢。令人意外的是,UI测试模板中建议使用重启的这种方式,而不是通过导航,因为这种方式在大多数情况下是不必要的。
第二次测试表明在一般得情况下,XCTest比KIF稍微快一点。当然也得注意,虽然这里只是节省了10秒,但是在一个比较大的并且一天需要执行好多次的一组测试中,还是能够节省不少的。
最后一个测试显示XCTest是十分亮眼的。然而取消动画对于每个项目来说只是一个可选项。想象一下,项目中有许多自定义的动画,测试需要验证这些动画是否按照我们预期的执行。
工作区使用不同得启动环境去控制app的配置,将这些测试分配到不同得类中。
class UITests: XCTestCase {
private var launched = false
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
launchIfNecessary()
}
private func launchIfNecessary() {
if !launched {
launched = true
app.launchEnvironment = [“animations”: “0”]
app.launch()
}
}
func testDetail() { ... }
func testPopover() { ... }
func testActionSheet() { ... }
}
AppDelegate设置启动环境的配置。
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if NSProcessInfo.processInfo().environment[“animations”] == “0” {
UIView.setAnimationsEnabled(false)
}
...
return true
}
}
这个能够减少你等待测试完成的时间。
结论(Conclusion)
XCTest使简单得UI测试更加容易。但是比较高级的测试将会很难或者不可能。KIF更加零落。
然后跟KIF比较,XCTest在找到UI elements上更加轻巧、快速。另外取消动画,XCTest运行相当快。
尽管我希望将UI Test集成到项目中,但是我还是放弃在我的项目中重写他们。我不能找到一个重写所有测试的方法。