本文由 BenBeng 翻译自shinobicontrols iOS9 Day-by-Day :: Day 2 :: UI Testing
在任何软件的开发中,自动化UI测试都是很重要的。它能快速发现你应用中的问题,在发布之前进行一次成功的配套测试能减少许多问题。在iOS平台目前是通过UIAutomation来完成自动化测试,它的用例是用JavaScript写的。这需要打开Instruments,在其中编写和运行脚本。这个流程实在是慢得出奇而且要花很长时间来适应。
在Xcode 7 中,Apple引入了一种新的方式来在你的应用中进行UI测试。UI testing允许你找到UI元素并与之交互,还能检查属性和状态。UI testing已经完全集成进Xcode 7 的测试报告了,可以和单元测试一起执行。在Xcode 5 中XCTest就已经集成到Xcode 的测试框架了,而在Xcode 7 中,XCTest已经拥有进行UI测试的能力了。这样你就能在检查UI状态的时候执行断言了。
为了让UI Testing能够工作,框架需要能够访问UI中的各个元素,这样才能执行它们的动作。你可以定义测试要在哪个特定的点进行点击和轻扫,但是这在不同屏幕大小的设备上就不那么好使了,又或者你换算出UI中元素在不同设备上的位置。
这时候accessibility就派上用场了。Accessibility是Apple很早之前构建的一个框架,它能帮助一些行动不便的用户来更好地使用你的应用。它为你的UI提供了丰富的语义数据,这能让不同的Accessibility功能给行动不便的用户展现你的应用。有很多功能都是现成的,直接就能在你的应用中使用,但是你可以(也应该)使用Accessibility的API来改进Accessibility关于UI的数据。在很多场景下这都是必需的,比如对一些自定义的控件,Accessibility就不清楚你的API要做什么。
UI Testing可以通过你的应用提供的Accessibility功能来与你的应用连接,这样就解决了设备大小不一的问题。如果你重新调整了UI中的某些元素,你也不用重写整套测试。实现Accessibility不仅是为了使用UI Testing,也能帮助行动不便的用户更好地使用你的应用。
当设置好可以访问的UI之后,你可能就想创建一些UI测试了。编写UI测试耗时又无聊,如果你的UI比较复杂,那这还有些难度。多亏在Xcode 7中,Apple引入了UI Recording,它能让你创建新的测试,并扩展原有的测试。当UI Recording打开之后,当你在真机或模拟器上与应用交互时,代码会自动生成。现在我们已经大概了解了UI Testing是怎么回事,是时候开始使用它了。
我们会使用新的UI测试工具构建一个demo来展示UI Testing是怎么工作的。如果你想一起做并看到结果,这有最终完成的demo( Swift? 或者Objective-C)。
当你在Xcode 7中创建新工程时,可以选择是否要包含UI测试。这会为你设置一个占位的UI Test target,并且配置好了所需的内容。
demo中的项目设置很简单,但是足够我们展示在Xcode 7中UI Testing是如何工作的了。
menu view controller 包含一个switch和一个button,button连接到detail view controller。当switch“关闭”时,button应该不可点击,跳转也就不可能发生。detail view controller包含一个button,点击button会增加label中的值。
当设置好UI并且可用时,我们就可以编写一些UI测试来确保代码的改动不会影响功能。
在我们开始录制动作之前,必须要决定需要断言什么内容。我们可以使用XCTest框架来对UI中的某些内容进行断言,现在框架中已经包含下面三个新API。
XCUIApplication。这是你正在测试的应用的代理。它能让你启动应用,这样你就能执行测试了。它每次都会新起一个进程,这会多花一些时间,但是能保证测试应用时的状态是干净的,这样你需要处理的变量就少了些。
XCUIElement。这是你正在测试的应用中UI元素的代理。每个元素都有类型和标识符,结合二者就能找到应用中的UI元素。所有的元素都会嵌套在代表你的应用的树中。
XCUIElementQuery。 当你想要找到某个元素时,就会用到 element query。每个 XCUIElement 里都包含一个query。这些query搜索 XCUIElement 树, 必须要找到一个匹配的。否则当你视图访问该元素时,测试就会失败。 例外是exists 属性,你可以使用这个属性来检查一个元素是否展示在树中。 这对于断言很有用。 更一般地你可以使用 XCUIElementQuery 来找到对accessibility可见的元素。Query会返回结果的集合。
现在我们已经了解了API,可以开始编写一些测试了。
Test 1– 确保当switch关闭时不会发生跳转
首先我们必须要定义一个方法来写测试。
func testTapViewDetailWhenSwitchIsOffDoesNothing(){ }
当定义好方法之后,我们把光标移到方法的括号里,然后点击Xcode 窗口下方的录制按钮。
应用会马上启动。点击关闭switch,然后点击“View Detail” 按钮。在testTapViewDetailWhenSwitchIsOffDoesNothing??方法里应该会出现下面的内容。
let app = XCUIApplication() app.switches["View Detail Enabled Switch"].tap() app.buttons["View Detail"].tap()?
现在再点击录制按钮,录制应该就会停止了。我们可以看到应用没有显示detail view controller,但是目前测试没有办法知道。我们必须加一个断言来判断没有发生跳转。我们可以检测导航栏的title。这可能不适合所有情况,但对我们这个例子来说足够了。
XCTAssertEqual(app.navigationBars.element.identifier, "Menu")
在添加完这行代码之后再次执行测试,应该是能通过的。试着将“Menu”字符串改成“Detail”,应该就失败了。下面就是这个测试的完整代码了,其中添加了一些注释来解释每一步的操作。
func testTapViewDetailWhenSwitchIsOffDoesNothing() { let app = XCUIApplication() // Change the switch to off. app.switches["View Detail Enabled Switch"].tap() // Tap the view detail button. app.buttons["View Detail"].tap() // Verify that nothing has happened and we are still at the menu screen. XCTAssertEqual(app.navigationBars.element.identifier, "Menu") }?
Test 2 – 确保当switch打开时发生跳转
第二个测试和第一个类似,所以就不详细讲了。唯一的区别是,当switch打开时,应用应该加载detail页面,XCTAssertEqual会检查这个。
func testTapViewDetailWhenSwitchIsOnNavigatesToDetailViewController() { let app = XCUIApplication() // Tap the view detail button. app.buttons["View Detail"].tap() // Verify that navigation occurred and we are at the detail screen. XCTAssertEqual(app.navigationBars.element.identifier, "Detail") }
Test 3 – 确保“Increment Value”按钮会增加label中的值
在这个测试中,我们要检查当用户点击“Increment Value”按钮,label中的值会增加1。测试中的前两行代码和之前的类似,所以我们可以直接从之前的测试中复制粘贴。
let app = XCUIApplication() // Tap the view detail button to open the detail page. app.buttons["View Detail"].tap()
下一步我们需要能访问这个button。我们之后要点击几次button,所以我们把它作为一个变量保存下来。与其我们手动输入代码找到这个button,而且还需要调试,不如直接使用录制功能,点击“Increment Value”按钮即可。这样就得到了下面的代码。
app.buttons["Increment?Value"].tap()
现在我们可以停止录制,把代码改成下面这样的:
let incrementButton = app.buttons["Increment Value"]
这样我们就不用手动输入代码来找这个button了。我们使用同样的方法来找到显示值的label。
let valueLabel = app.staticTexts["Number Value Label"]
现在我们已经有UI元素,就可以和它们交互了。在这个测试中我们会检查点击button十次之后,label的值是否也相应地更新了。我们可以录制十次点击,但是我们之前已经保存这些元素了,所以可以在循环中完成。
for index in 0...10 { // Tap the increment value button. incrementButton.tap() // Ensure that the value has increased by 1. XCTAssertEqual(valueLabel.value as! String, "/(index+1)") }
这三个测试根本算不上全面,但它们应该给你开了一个好头,你可以在这之上很轻松地扩展。你可以自己写一个测试来检查当button可点击时,你能跳转,如果关闭switch,又是否能跳转。
某些时候,在录制时,当你点击了一个元素,你可能会注意到生成的代码看上去不太对。这通常是因为你正在交互的元素对Accessibility不可见。你可以使用Xcode的Accessibility Inspector来检查是不是这种情况。
当打开Accessibility Inspector之后 ,如果你按下CMD+F7,并把鼠标悬停在模拟器中的元素上,就能看到鼠标指针下面元素的详细信息。这应该能帮助你解决为什么Accessibility找不到你的元素。
当你解决了问题之后,打开interface builder,在 identity inspector你可以找到Accessibility面板。在这你能打开Accessibility,设置hints,、labels 、identifiers和 traits。这些都是很有用的工具来让Accessibility访问你的界面。
测试失败怎么办?
如果一个测试失败了,而你不是很清楚为什么,有一些方法能帮助你解决问题。首先你可以在Xcode的Report Navigator中找到测试报告。
当打开这个界面,鼠标悬停在测试中的某一步,你会在测试事件的右边看到一个小眼睛图标。点击图标,会弹出你的应用在特定步骤的截图。这能让你可视化查看UI的状态,找到到底是哪里出了问题。 ?
和单元测试一样,在UI测试中你也可以添加断点,这样你就能调试并找到问题。你可以输出view的层级,检测accessibility属性来查看为什么测试会失败。
自动化UI测试是提升应用质量的一种很好的方式。我们已经看到了在Xcode中设置、执行UI Testing是多简单,为你的应用添加Accessibility功能不仅能帮助测试你的应用,而且也能帮助行动不便的用户更好地使用你的应用。
Xcode中的UI Testing的最好的新功能之一,是能在持续集成服务器中执行测试。Xcode bots提供对此的支持,而且 command line 支持当UI测试失败时会立即发出通知。
关于Xcode中新的UI Testing的更多内容,我建议你观看WWDC session 406,? UI Testing in Xcode 。还推荐阅读? Testing in Xcode Documentation 和? Accessibility for Developers Documentation 。