用户期待iOS应用的高水准用户体验,于是你须要设计、开发和测试你的应用来知足这一不断上升的指望。为了达成这个目标,你会投入多少时间进行原始人工的用户界面测试?你知道这活儿怎么干…从Xcode启动你的应用,并不断的用手指点击同一些按钮来确保你的设计中没有退步。相比这些,你固然更愿意作其它一些事情?html
考虑下使用Xcode 5中加强的UI测试,还有 OS X Server中支持的持续集成 这篇文章展现了苹果公司为开发者贡献的最好的工具。ios
你也许会说,这很棒,可是如何让那些简单的用户动做测试变得自动化呢,例如确保一个在合适区域的双击或触摸后会进入正确的视图?即便是测试脚本和机器人也不会有能在屏幕上滑动的电容式触摸手指…呃…它们有手指么?git
在这篇教程中,你将会学习到一些有关 KIF 的东西(Keep it Functional), 它是一个开源的用户界面测试框架. 使用 KIF, 并利用 iOS中的辅助功能 API, 你将可以编写模拟用户输入(例如点击、触摸和文本输入)的测试。github
它提供自动化的、真实的用户界面操做, 帮助放松你的心情,于是你就能够只去关注本身的杀手级应用了,而不是在UI测试上耗费你职业生涯的一半时间。编程
让咱们开始测试工做吧!segmentfault
咱们以一个叫作茄薯Solanum(一个马铃薯品种的名字)的计时器应用为例。这是一个基于番茄工做法的应用。xcode
它是这么运做的:浏览器
这个应用就是一个可以持续跟踪时间区段的简单计时器。安全
这款应用以后能使你的开发工做变得更加高效!app
从 这里 下载并解压缩入门项目.注意 KIF 是一个独立的项目,它是为茄薯应用构建一个用于测试目标的库。所以你须要双击 solanum.xcworkspace 以便在Xcode中打开项目,而不是 solanum.xcodeproj。在项目导航视窗中找到这两个项目,以下图所示:
将应用的目标设置到solanum,而后选择3.5或者4英寸的iPhone模拟器目标。不要使用64位,由于KIF在64位模拟器上有一些问题。构建并运行这个应用。四处瞧瞧,了解一下,而后切换到Settings。
应用有一个可以加速时间流逝的调试模式。所以你能够设置一个20分钟的计时器,测试时花10秒完成这个过程。这只是为了帮助你测试应用,你应该不会想花20分钟等着看它运行吧。
开启调试模式来加速计时器,接着点击Clear History按钮,而后在确认弹出视图上点击Clear。这几步确保了你是在一个干净的环境中开始测试做业。返回到Xcode并把应用停下来.
在项目浏览器中,展开solanum项目。 右击UI测试文件夹并点击New File…来加入你的新测试用例。
选择 iOS\Cocoa Touch\Objective-C class
并点击 Next
, 将类命名为 UITests 并使其成为 KIFTestCase 的子类。
点击Next
并确保文件已经被添加到UI Tests
目标中, 而不是 solanum 目标。 而后点击接下来Create
保存文件.
KIFTestCase是SenTestCase的一个子类. 那意味着你拥有了大部分的标准 OCUnit 测试方法和机制可使用,若是你已经很熟悉单元测试的话。
打开UITests.m
,并在@implementation
一行添加以下的方法:
- (void)beforeAll { [tester tapViewWithAccessibilityLabel:@"Settings"]; [tester setOn:YES forSwitchWithAccessibilityLabel:@"Debug Mode"]; [tester tapViewWithAccessibilityLabel:@"Clear History"]; [tester tapViewWithAccessibilityLabel:@"Clear"]; }
beforeAll 是一个在全部测试运行以前被调用一次的特殊方法。你能够为你这里运行的测试设置任何实体变量和初始化条件.
tester 对象是指定的KIFUITestActor 类的一个缩略名称。这个类包含了模拟用户动做的方法,包括触摸和滑动.
tapViewWithAccessibilityLabel 这也许是最常被用到的测试动做方法。正如其名称所显示的,它能够在给定的辅助标签模拟在视图上的触击。在大多数状况下,辅助标签和可视的文本标签(例如按钮组件)是配套的。不然你就须要手动设置辅助标签.
一些控件,诸如 UISwitch,更加复杂,须要比简单的触击更复杂的步骤来触发。 KIF 提供了一个特殊的 setOn:forSwitchWithAccessibilityLabel: 方法来改变一个切换的状态.
总之,这个方法对测试动做进行了四步操做:
这步看起来很眼熟? 应该是熟悉的! 它们就是以前章节中你手动进行的操做!
点击Product Test
菜单或者使用Command-U运行测试。应用开始运行,而后KIF接手,自动开启调试模式并清除历史记录.
若是你启用了通知,Xcode将告知你测试的状态:
有时候测试运行器或者说KIF可能会有一点挑剔,拒绝运行测试,你只能看到一个空白的模拟器屏幕。若是遇到这样的状况,那么:
Product\Clean
)这一过程确保了模拟器正在运行,而且你正在最新的构建上进行操做。通过上面的几步以后,试试再一次运行测试。问题应该会消失
若是问题依然存在, 你能够参考 KIF 故障排除步骤。
如今你已经在 beforeAll 中有了一个测试前置动做, 是时候加入你的第一个测试了!
该应用程序有一个标准的选项卡界面,三个选项卡中,每个都使用了UINavigationController。先作个热身吧,测试一下:
选项栏按钮自动地设置成相似文本标签这样的辅助标签,因此KIF能够经过设置选项栏中的“历史”、“定时器”、“设置”标签找到历史、计时器和设置。
历史选项卡有一个显示全部定时器执行任务的表视图。从solanum
组里打开HistoryViewController.m
,在viewDidLoad末尾处添加如下几行:
[self.tableView setAccessibilityLabel:@"History List"]; [self.tableView setIsAccessibilityElement:YES];
这样,表视图的辅助标签就设置好了,KIF可以找到它。一般状况下,一个表视图仅在空的状况下才能被访问。表视图的单元格有可能被当作是一个目 标,因此表视图它自己会被藏在辅助功能的API之下。基本上,辅助功能的API在默认状况下假定表视图是不重要的。就辅助自己而言,这是合理的。不过若是你可能想在KIF中引用表视图,那么可使用setIsAccessibilityElement:
,它确保表视图始终是可访问的,无论其是否有内容。
对于使用辅助功能的用户来讲,非空的表格视图能够访问会使操做复杂化。在你的应用中,你能够把相应的代码包裹在#ifdef DEBUG
和#endif
指令之间,这样这些代码就只会被编译到调试构建中。DEBUG
预处理器宏已经在Xcode项目模板中被提早定义好了。
Timer 选项卡有一些控件,而“任务名称”文本框放在了视图的顶部,以方便用户使用。你无需经过代码来设定标签,只需能够打开 Main.storyboard
并找到Timer View Controller
视图. 选择task name text field
#。
打开Utilities
,选择Identity Inspector
。提示:从左边数第三个图标, 或者使用快捷键``⌥ ⌘ 3`。
在inspector的Accessibility
下,Label一栏中输入Task Name
。当心,这是大小写敏感的。确保输入了大写的 T 和 N!
Settings 面板已经设置好了视图的辅助标签,你能够进行下一步操做了!
在你本身的项目中,你将须要继续填写辅助标签,你能够经过代码,也能够经过上述 Interface Builder的方式。为了方便,示例应用余下来的选项已经设置好了。
回到 UITests.m
, 在beforeAll
以后加入这个方法:
- (void)test00TabBarButtons { // 1 [tester tapViewWithAccessibilityLabel:@"History"]; [tester waitForViewWithAccessibilityLabel:@"History List"]; // 2 [tester tapViewWithAccessibilityLabel:@"Timer"]; [tester waitForViewWithAccessibilityLabel:@"Task Name"]; // 3 [tester tapViewWithAccessibilityLabel:@"Settings"]; [tester waitForViewWithAccessibilityLabel:@"Debug Mode"]; }
测试运行器会在运行时寻找全部以test
开头的方法, 而后按字母顺序运行它们。这个方法以test00
开头,所以它会在你以后加入的方法以前运行,那些是以诸如test10
、test20
这样的名字开头的。
这些方法会进行相似一些动做: 触击标签栏,检查指望的视图是否被显示在屏幕上。waitForViewWithAccessibilityLabel:
默认的超时时间是10秒。若是在这段时间内预期的东西没有显示,测试就宣告失败。
点击 Product\Test
或者 Command-U 运行测试。你会注意到beforeAll
中的步骤会清除掉历史记录,接着test00TabBarButtons
会接管并按顺序切换到历史、定时器和设置标签。
哈哈,你看到了吗?你只是编写并运行了一个接口测试, 就会看到你的小应用本身在“启动”了!恭喜了! 你正在掌握自动化UI测试的旅途上。
固然,切换标签很棒,不过咱们也该关注一下更加真实的动做:输入文本,触发模式对话框,以及选中表视图中的一行。
测试应用中内置了更改工做时间、休息时间、推荐的重复次数的预设功能。若是你很好奇,想查看他们的定义,那么能够浏览一下PresetsViewController.m
中的presetItems
。
每一个预设自己就能够成为一个测试。可是做为其余测试的一部分更有效。所以咱们把它独立出来成为一个辅助方法。
把如下的方法添加到UITests.m
的实现模块中去:
- (void)test00TabBarButtons { // 1 [tester tapViewWithAccessibilityLabel:@"History"]; [tester waitForViewWithAccessibilityLabel:@"History List"]; // 2 [tester tapViewWithAccessibilityLabel:@"Timer"]; [tester waitForViewWithAccessibilityLabel:@"Task Name"]; // 3 [tester tapViewWithAccessibilityLabel:@"Settings"]; [tester waitForViewWithAccessibilityLabel:@"Debug Mode"]; }
这里的第一步是切换到计时器标签,而后触击导航栏中的预设按钮。当“预设列表”表视图出现的时候,触击指定的一行。
触击行会致使视图控制器会消失,因此要用waitForAbsenceOfViewWithAccessibilityLabel:
来保证它在你继续下一步以前已经消失了。
你注意到了吗?这个方法不是以test
为开头的。测试运行器是不会自动运行它的。你须要在测试中手动调用这个方法。
如今,添加如下测试方法到UITests.m
:
- (void)test10PresetTimer { // 1 [tester tapViewWithAccessibilityLabel:@"Timer"]; // 2 [tester enterText:@"Set up a test" intoViewWithAccessibilityLabel:@"Task Name"]; [tester tapViewWithAccessibilityLabel:@"done"]; // 3 [self selectPresetAtIndex:1]; // 4 UISlider *slider = (UISlider *)[tester waitForViewWithAccessibilityLabel:@"Work Time Slider"]; STAssertEqualsWithAccuracy([slider value], 15.0f, 0.1, @"Work time slider was not set!"); }
KIF测试操做有可读性很好的名字,看看你能不能发现接下来会发生什么?把它当作是一个……测试!没错,我故意用了双关语。
好的,这就是实际发生的步骤:
在代码最后一部分,你会发现一个有用的技巧:waitForViewWithAccessibilityLabel:
。它不只仅能够等待视图出现,还能够返回一个指针到视图中去。这样,你能够将返回值类型转换成UISlider,以便匹配适当的类型。
因为KIF测试用例也是常规的OCUnit测试用例,所以你能够调用标准的STAssert断言宏。断言是运行时的检查项,若是某些条件不知足,断言会致使测试失败。最简单的断言是STAssertTrue
,用来判断传入参数是否为true。
STAssertEquals
会检查传入的前两个参数是否相等。滚动条的值是float类型,因此要注意匹配他们的类型。所以,在断言里咱们使用15.0f
。此外,你还须要注意浮点数的偏差,这是由于浮点数的值不多是100%精确的。好比说,15.0
实际上可能被存储为 15.000000000000001
。因此STAssertEqualsWithAccuracy
是一个更好的选择,它的第三个参数指定误差范围。在这种状况下,若是该值的偏差在-0.1到+0.1之间,断言仍然能经过。
用Command-U运行测试。你会看到这三个序列:beforeAll
清除历史记录,test00TabBarButtons
在每一个标签间切换,最后的是test10PresetTimer
,输入一个任务名并选择一个预设。
这是另外一个成功的测试用例!此时,你的测试模仿用户的触击,甚至键盘输入,可是还有更多惊喜在后头!
下面给出一个例子,定时循环跑一个应用程序,应用的用户可能选择:工做8分钟,休息2分钟,工做8分钟,休息2分钟,最后工做8分钟。接下来是一个较长的休息。而后再从新启动应用程序。
以上例子的参数是:
接下来的KIF测试须要输入这些参数,而后点击“开始工做”按钮来启动定时器。添加如下的方法到UITests.m
,直接加在你以前添加的测试以后:
- (void)test20StartTimerAndWaitForFinish { [tester tapViewWithAccessibilityLabel:@"Timer"]; [tester clearTextFromAndThenEnterText:@"Test the timer" intoViewWithAccessibilityLabel:@"Task Name"]; [tester tapViewWithAccessibilityLabel:@"done"]; [tester setValue:1 forSliderWithAccessibilityLabel:@"Work Time Slider"]; [tester setValue:50 forSliderWithAccessibilityLabel:@"Work Time Slider"]; [tester setValue:1 forSliderWithAccessibilityLabel:@"Work Time Slider"]; [tester setValue:8 forSliderWithAccessibilityLabel:@"Work Time Slider"]; [tester setValue:1 forSliderWithAccessibilityLabel:@"Break Time Slider"]; [tester setValue:25 forSliderWithAccessibilityLabel:@"Break Time Slider"]; [tester setValue:2 forSliderWithAccessibilityLabel:@"Break Time Slider"]; }
由于这个测试会在test10PresetTimer
(这个测试会设置任务名)后马上运行,你可使用clearTextFromAndThenEnterText:intoViewWithAccessibilityLabel:
,而不是使用enterText:intoViewWithAccessibilityLabel:
清除任何现存的文本。
最后,这里有几个setValue:forSliderWithAccessibilityLabel:
调用。这是UISlider指定的给新变量赋值的方法。注意,并不老是很精确。KIF其实是模拟触击事件去设置新的值,有时候像素的计算会有所误差。不过这没什么大问题,由于咱们的手指也不是特别精确!
你仅须要设置每一个滚动条的值一次。屡次的调用仅仅是为了反复,因此你会看到KIF不停地更改值。
使用Command-U运行测试。
剩下的UI测试是在UIStepper控制器中设置重复的次数,还有“开始工做”按钮。“开始工做”按钮很简单——你可使用tapViewWithAccessibilityLabel:
模拟触击。但对于UIStepper来讲,咱们须要采用一些迂回策略。
以下图所示,UIStepper控制器分为两部分,所以,若是你仅仅调用tapViewWithAccessibilityLabel:
,很难肯定会发生什么。
KIF会开始尝试触击控制器的中心。若是它是一个不可触击的区域,它会接下来尝试点击左上角、右上角、左下角、右下角。结果显示,触击+、-以前的分界线会触发+,因此它会不断地增长重复次数。
可是若是你想减小次数呢?有一些变通的方法的,例如深刻子视图找到减号的按钮,另外一种方法则是使用KIF的tapScreenAtPoint:
测试操做,能够模拟触击屏幕上的任意点。
你对CGGeometry
了解得如何?如何计算出+/-按钮在窗口中的坐标?准备好来证实你的实力没?为何不接受这个挑战呢?这彻底是可选的, 你能够直接跳到下面去查看代码和计算。可是,若是想测验一下你的技能,能够尝试在偷看答案以前先本身写一下代码。
请注意,你很快须要将下面的代码添加到测试中,这个任务只是测试你的数学和CGGeometry能力。
首先,你须要引用UIStepper
:
UIStepper *repsStepper = (UIStepper*)[tester waitForViewWithAccessibilityLabel:@"Reps Stepper"];
而后你能够找到中心点:
CGPoint stepperCenter = [repsStepper.window convertPoint:repsStepper.center fromView:repsStepper.superview];
UIStepper能够是在嵌套视图内,而你真正想要的是UIStepper的中心点相对于整个窗口的坐标,所以须要调用convertPoint:fromView:
。
如今,你获得了中心点相对于窗口的位置,你能够减小x轴的值来获取到减号的按钮,或者增长x轴来获取加号的按钮。
CGPoint minusButton = stepperCenter; minusButton.x -= CGRectGetWidth(repsStepper.frame) / 4; CGPoint plusButton = stepperCenter; plusButton.x += CGRectGetWidth(repsStepper.frame) / 4;
若是增长或者减小UIStepper宽度的1/4,就会正好到达加号、减号按钮的中间位置。
瞧!这两个点,至关于UIStepper控制器中的加号/减号按钮的中心点。
触击屏幕的指定点是万不得已的方法,但有时它是测试UI的惟一的方法。例如,你也许会有一个自行定制的控制器没有实现UIAccessibility Protocol。
好的,你已经到了定时器测试的最后一步了。你开始意识到用KIF作UI测试的潜力了么?很好!
最后的步骤是设置重复次数,而后启动定时器。添加如下代码到UITests.m
中的test20StartTimerAndWaitForFinish
的结尾:
// 1 UIStepper *repsStepper = (UIStepper*)[tester waitForViewWithAccessibilityLabel:@"Reps Stepper"]; CGPoint stepperCenter = [repsStepper.window convertPoint:repsStepper.center fromView:repsStepper.superview]; CGPoint minusButton = stepperCenter; minusButton.x -= CGRectGetWidth(repsStepper.frame) / 4; CGPoint plusButton = stepperCenter; plusButton.x += CGRectGetWidth(repsStepper.frame) / 4; // 2 [tester waitForTimeInterval:1]; // 3 for (int i = 0; i < 20; i++) { [tester tapScreenAtPoint:minusButton]; } [tester waitForTimeInterval:1]; [tester tapScreenAtPoint:plusButton]; [tester waitForTimeInterval:1]; [tester tapScreenAtPoint:plusButton]; [tester waitForTimeInterval:1]; // 4 [KIFUITestActor setDefaultTimeout:60]; // 5 [tester tapViewWithAccessibilityLabel:@"Start Working"]; // the timer is ticking away in the modal view... [tester waitForViewWithAccessibilityLabel:@"Start Working"]; // 6 [KIFUITestActor setDefaultTimeout:10]
这里介绍在最后阶段中即将会发生什么:
UIStepper
的加号和减号按钮的坐标。waitForTimeInterval:
调用增长了延时,这样你能够看到步数值的变化——不然的话,对于肉眼而言,它的变化太快了。一旦你保存文件,你会发现有方法声明一侧会出现一个小的棱形图标:
这是一个运行单个测试的按钮。所以,你能够在测试环境下只运行这个方法,而不须要运行整套测试用例。优雅!点击菱形按钮来运行这个测试,你会看到模拟器启动,并运行测试用例。只要坐在一旁观看它输入任务名,滑动滚动条,以及定时器的时间流动就能够了。不用动一根手指,你就能测试UI了。
成功了!如今你能够成功设置测试,帮助你完成一系列UI控制器的操做。
若是你经过菱形按钮跑单个测试,它只跑一个简单的方法而不会在开头调用beforeAll
。若是你的测试是依赖于beforeAll
的,你仍然须要跑整套完整的测试。
“去工做!”模式视图控制器中有一个放弃按钮可让用户取消一个时间周期。你仍能够测量已经工做的分钟,即便很早就结束了测试。这个数据仍然会被记录到历史上,但会被标记为整个周期没有跑完。
你不用相信个人话,能够本身尝试一下。只需作一些相似上述测试的东西。设置定时器的参数,触击*开始工做按钮,而后触击放弃
按钮。
不要立刻触击“放弃”按钮——定时器须要运做一段时间,应用程序才开始建立历史记录。因此,你能够将鼠标移动到测试上,手动中止测试。或者,你能够编程设定它在你选定的时间和位置。若是你生性犹豫,你能够跳过这个挑战。然而,若是你喜欢编写应用去帮你处理各类琐事,就试一下吧!你知道如何在“开始工做”和“放弃工做”之间增长一点延时么?
你能够调用waitForTimeInterval:
在你的测试里增长延时。
在UITests.m
中添加下列的测试方法,加在其余测试的下面:
- (void)test30StartTimerAndGiveUp { [tester tapViewWithAccessibilityLabel:@"Timer"]; [tester clearTextFromAndThenEnterText:@"Give Up" intoViewWithAccessibilityLabel:@"Task Name"]; [tester tapViewWithAccessibilityLabel:@"done"]; [self selectPresetAtIndex:2]; [tester tapViewWithAccessibilityLabel:@"Start Working"]; [tester waitForTimeInterval:3]; [tester tapViewWithAccessibilityLabel:@"Give Up"]; [[tester usingTimeout:1] waitForViewWithAccessibilityLabel:@"Start Working"]; }
在确保你位于正确的选项卡以后,设置任务名而后选择好预设。而后启动定时器,在放弃以前等待3秒。
方法的最后一行等待模式视图控制器消失,返回主界面。记住,默认的超时时间是10秒,可是实际上不须要这么长时间——触击“放弃”按钮,模式视图控制器会当即消失。
在以前的测试中,你使用了类方法setDefaultTimeout:
为全部测试操做设置全局的超时时间。在这里,你能够调用usingTimeout:
为某一步定制的超时时间。
保存文件并点击菱形按钮运行单个测试。当定时器开始的时候,你能够发现它在放弃、返回主界面以前会等待3秒。
以前咱们没有怎么关注历史选项卡,如今该轮到它出一下风头了。若是你按照练习一步一步来进行操做的话,那么你至少会有一条历史记录。构建并运行该应用,而后切换到历史选项卡。
历史表视图实现了最新的iOS7删除手势——当你把整行往左边拖动,而后触击出现的“删除”按钮。这是你的下一个测试!这要求历史中至少有一条记录,因此你跑单个测试的时候要当心。你必须保证历史记录中有内容,不然就没什么能够测的!安全起见,你须要完整地运行整套测试,由于前面的测试会建立一些历史记录供你使用。记住,测试的运行顺序是根据字母顺序排列的,因此你能够很放心,前面的测试会建立一些历史记录的。
若是你看一下HistoryViewController.m
中的tableView:cellForRowAtIndexPath:
,你会发现每一格都能获得一个辅助标签,好比“第3行第0个”之类的。这有助于KIF找到这一行。但在真实环境下这种标签很糟,因此不要在真正的应用中使用。在示例工程中,咱们使用#ifdef
来确保这些只出如今调试构建中。你应该在你本身的应用中作相似的事情。请确保在发布构建的时候,将辅助标签设为一些有用的内容,切实为用户提供辅助功能。
如今,打开UITests.m
并添加以下的测试方法:
- (void)test30StartTimerAndGiveUp { [tester tapViewWithAccessibilityLabel:@"Timer"]; [tester clearTextFromAndThenEnterText:@"Give Up" intoViewWithAccessibilityLabel:@"Task Name"]; [tester tapViewWithAccessibilityLabel:@"done"]; [self selectPresetAtIndex:2]; [tester tapViewWithAccessibilityLabel:@"Start Working"]; [tester waitForTimeInterval:3]; [tester tapViewWithAccessibilityLabel:@"Give Up"]; [[tester usingTimeout:1] waitForViewWithAccessibilityLabel:@"Start Working"]; }
如今介绍上面方法的做用:
用Command-U运行整套测试,全部的测试会顺序执行。
如今,你的测试覆盖了整个应用的基本流程了——从数据重置到屡次运行定时器,再到验证和删除历史记录。
若是你要继续开发这个应用程序,这些测试会是一个很好的基础,确保界面没有退步。这意味着在主界面变化的时候你须要更新你的测试——测试就像是你应用的规格说明,须要及时更新才能发挥做用。
到目前为止,你应该对KIF的可能性有很好的了解,脑子里也应该有很多主意,如何利用这个高效的功能测试工具来测试你本身的应用程序。添加KIF到你本身的项目的时候,你能够查看这个文档。你能够手动添加KIF或者使用很是方便的CocoaPods依赖管理。
KIF是一个开源项目,且有许多新功能还在不断开发之中。例如,在写这篇教程的时候,下一个KIF的发布版将会包含截屏的功能。这意味着能够在跑测试的时候经过屏幕截图来查看全过程当中的关键点。听起来这不是比用肉眼观察KIF点击和拖动整个过程好上千万倍么?KIF正变得愈来愈好了,因此学习如何使用它,是一个很好的自我投资。
最后,因为KIF测试用例是OCUnit的子类,并在标准的Xcode5测试框架下运行,你可使用持续集成来跑这些测试。当你干别的事情的时候,你拥有了一个可以像人的手指同样触控的机器人去测试你的应用程序。太棒了!
你能够在这里下载整个示例项目的代码。测试快乐!