iOS 中使用 KIF 测试 UI

用户期待从iOS应用获得一个高水平的体验,因而需要你来设计,开发和测试你的应用来满足这一不断上升的期望. 想想只是为了这一刻: 你会投进入多少时间进行原始人工的用户界面测试?你知道这活儿怎么干…从Xcode启动你的应用,并不断的用手指点击同一些按钮来确保你的设计中没有让应 用退步的体验. 当然,还有其它一些你更愿意做的事情?

取而代之,考虑考虑Xcode 5中增强的UI测试吧,还有 OS X Server中支持的持续集成 这篇文章展示了苹果公司为开发者贡献的最好的工具. 你也许会说,那很棒,而你如何让那些简单的用户动作测试变得自动化呢,就像确保一个在合适区域的双击或触摸后会进入正确的视图?即使是测试脚本和机器人也不会有能在屏幕上滑动的电容式触摸手指…或者…它们会有么?

在这篇教程中,你将会学习到所有有关 KIF 的东西(“保持功能 Keep it Functional”), 它是一个开源的用户界面测试框架. 使用 KIF, 并利用 iOS中的辅助功能 API, 你将能够编写模拟用户输入,诸如点击,触摸和文本输入,这样的测试. 这些给予你的应用自动化的,真实的用户界面操作, 并比帮助放松你的心情,因而你就可以只去关注自己的杀手级应用了 – 不用在UI测试上耗费你生涯的一半时间.

让我们来开始测试工作吧!

入门

示例是一个叫做茄薯Solanum(一个马铃薯品种的名字)的的计时器应用,它基于Pomodoro 时间盒子方法. 这是是它的运作方式: 按照设定好的分钟数你工作的一段时间,休息一下,然后重复工作和休息. 进过这样若干个循环之后, 你就会休息一段更长的时间. 这个应用只是一个能够持续跟踪时间区段的简单计时器. 轻松的使用了这款应用之后,就能使得你的开发工作变得更加高效!

220140318104938

从 这里 下载并解压缩入门项目.注意 KIF 是一个分开独立的项目, 而它的角色是为茄薯应用构建一个用于测试目标的库. 你需要双击 solanum.xcworkspace 来在Xcode中打开项目,而不是 solanum.xcodeproj. 在项目导航视窗中找到这两个项目,应该如下面图示中这样.

320140318104958

将应用的目标设置到solanum,并选择3.5或者4英寸的iPhone模拟器目标.不要使用64位构建,因为在写这篇教程的时候,KIF貌似还不能完全兼容.构建并运行这个应用.各处都瞧一瞧,然后切换到设置Settings面板.

420140318105011

应用有一个能够加速时间的调试模式,因此你可以设置一个20分钟的计时器,而它将会在测试中用10秒钟走过这段时间. 这只是为了帮助你测试应用.你应该不会想花20分钟等着看它运行吧!

开启调试模式来加速计时器.接下来点击Clear History按钮,然后在确认弹出视图上点击Clear.这几步确保了你是在一个干净,对测试有的环境中开始测试作业.返回到Xcode并把应用停下来.

测试前操作

在项目浏览器中,展开solanum项目. 右击UI测试文件夹并点击New File…来加入你的新测试用例.

520140318105026

选择 iOS\Cocoa Touch\Objective-C class 并点击 Next. 将类命名为 UITests 并使其成为 KIFTestCase 的子类.

620140318105049

点击Next 并确保文件已经被添加到 UI Tests 目标中, 而不是 solanum 目标. 最后,点击接下来屏幕上的 Create 以保存文件.

KIFTestCase是SenTestCase的一个子类. 那意味着你拥有了大部分的标准 OCUnit 测试方法和机制可以使用, 考虑到你已经很熟悉的 单元测试.

打开UITests.m 并在@implementation 一行添加如下的方法:

beforeAll 是一个实际上只是在所有测试运行之前被调用一次的特殊方法. 你可以为你这里运行的测试设置任何实体变量和初始化条件.

tester 对象是指定的KIFUITestActor 类的一个缩略名称. 这个类包含了可以模拟用户动作的方法,包括触及还有在视图上滑动.

tapViewWithAccessibilityLabel: 也许是最常被用到的测试动作方法. 正如其名称所揭示的,它使用在给定的可访问标签上模拟在视图上的触击. 在大多数情况下,可访问标签都是匹配诸如按钮这种组件的可视的文本标签. 如果不是这样的,那就会如你在下一节将会看到了,你将需要手动设置访问标签.

一些控件,诸如 UISwitch, 相较而言更加复杂而比仅仅只需要简单的触击更甚的动作. KIF 提供了一个特殊的 setOn:forSwitchWithAccessibilityLabel: 方法来改变一个切换的状态.

总结起来:这个方法对测试动作进行了四步操作:

  • 触击“Settings” 选项卡按钮条.
  • 将 “调试模式Debug Mode” 切换到它的“开启”状态.
  • 触击 “Clear History” 按钮.
  • 在UIAlertView上触击“Clear”按钮.

这几部看起来很眼熟? 应该是熟悉的! 它们就是之前章节中你手动进行的操作!

到 Product\Test 或者在键盘上敲 Command-U 来运行测试. 你会看到应用运行了起来; 而后你会看到 KIF 接手,自动开启调试模式并清除历史记录.

如果你开启了通知,Xcode也将会告知你测试的状态:

720140318105113

有时候测试运行器或者说KIF可能会有一点过分讲究,而拒绝你运行测试, 在此状况下你将只能看到一个空白的模拟器屏幕. 如果发生了这样的事情:

  • 清理一下这样个项目(Product\Clean)
  • 构建并运行
  • 等待应用启动
  • 在Xcode中终止应用的运行

这一过程确保了模拟器正在运行,并且你正在最新的构建上进行操作. 在经过上面的几步之后,试试再一次运行测试. 问题应该没有了.

如果你还是继续遇到这样的麻烦, 再检查下 KIF 故障排除步骤.

现在你已经在 beforeAll 中有了一个测试前置动作, 是时候加入你的第一个测试了!

一个简单的测试:点击周围

该应用程序在这三个选项卡中,每一个均有标准的选项卡控制器层UINavigationController。在接下来的练习中,你将会做个热身,来决定是否:

  • 把面板适当地连接起来
  • 选项卡展示正确的视图

选项栏按钮自动地设置成类似文本标签这样的可访问标签,所以KIF可以通过设置选项栏中的“历史(History)”、“定时器(Timer)”、“设置(Settings)”标签找到历史、计时器和设置。

历史选项卡有一个显示所有定时器执行任务的表视图。从solanum组里打开HistoryViewController.m,在viewDidLoad末尾处添加以下几行:

表视图的可访问标签就设置好了,KIF也因此能够找到它。通常情况下,一个表视图仅在空的情况下才能被访问。如果是表视图的单元格有可能被当做是一个目 标,所以表视图它本身会被藏在可访问的API之下。本来可访问的API在默认情况下假设表视图是不重要的。可能有这样的用例需要根据可访问性,但是如果你 想在KIF中引用表视图就需要使它成为可访问的。setIsAccessibilityElement:要求确保表视图始终是可访问的,而不管其内容。

不同的应用中,如果用户使用了可访问特性(例如:VoiceOver)访问非空的表格视图会使得问题更加复杂化.在你的应用中,你可以把几行代码套 在#ifdef DEBUG 和 #endif 指令之间,以便这几行代码只被编译到你的调试构建中.DEBUG 预处理器宏已经在Xcode项目模板中被提前定义好了.

Timer 选项卡有几个你可以观察观察的控件,而“Task Name”文本域则被便利的放在了视图的顶部. 不同于通过代码来设置标签属性, 可以打开 Main.storyboard 并找到Timer View Controller 视图. 选择task name 选择文本域,再来进行设置.

820140318105131

打开 Utilities  面板,如果它没有显示出来的话 — 并选择Identity Inspector. 提示:它是从左边数第三个图标, 或者使用快捷键‘⌥ ⌘ 3′.

920140318105142

在这个窗口的 Accessibility 一栏的Lable域中输入“Task Name“. 要警惕一点,因为可访问选项对大小写是敏感的. 确保输入如显示的,是大写的 T 和 N!

1020140318105154

Settings 面板已经用Accessibility选项将视图设置好了, 因此你已经万事俱备,可以进入到下一步操作了!

在你的项目中,你将会需要继续在Accessibility选项中进行设置,通过代码方式或者如前面已经在Interface Builder中已经做过的. 为了方便,示例应用余下来的选项已经设置好了.

回到 UITests.m, 看看在 beforeAll之后的这个方法:

测试运行器会在运行时寻找所有以”test“开头的方法, 然后按字母顺序运行它们. 这个方法以名字“test00″ 开头,因此它会在你稍后加入的方法之前运行, 因为它们是以诸如“test10″, “test20″ 这样的名字开头的.

每三个一组的方法会进行类似一些动作: 在一个选项卡按钮条上触击, 并检查期望看到的视图被显示在屏幕上. 10 秒是waitForViewWithAccessibilityLabel:设置的默认时间. 如果在这段指定的时间没有显示预期的东西,测试就告失败.

选择 Product\Test 或者敲击 Command-U键运行这些测试. 你会看到beforeAll 中的步骤会清除掉历史记录, 而后test00TabBarButtons 会接手并按顺序切换到History, Timer 和 Settings 选项卡.

1120140318105212

那么,你怎么意识到测试已经发生了呢? 你只需要编写并运行一个借口测试, 就会看到你的小应用自己在“启动”了!恭喜了! 你正在掌控自动化UI测试的征程上.

用户输入

当然,切换标签是非常好看的,但是是时候去关注更加真实的动作:输入文本,触发模式对话框并选择一个表视图的行。

该测试应用程序有一些内置的可以更改工作时间、休息时间、一组推荐值的重复次数的预设。如果你想看看他们的定义,可以看看在PresetsViewController.m中的presetItems。

选择一个预设,这个自己本身就可以成为一个测试,但是这个动作成为其他测试的一部分会更有效。在这种情况下是值得把它独立开来成为一个辅助方法。

把以下的方法添加到UITests.m的实现模块中去:

第一步是切换到计时器(Timer)标签栏,然后点击导航栏中的预设按钮(Presets)。当“预设列表(Presets List)”表视图出现的时候,点击该行上的指定索引处。

点击行,视图控制器会消失,所以要用waitForAbsenceOfViewWithAccessibilityLabel:来保证它在继续下一步之前已经消失了。

你注意到了吗?这个方法不是以test为开头的。测试运行器是不会自动运行它的,而是需要你在自己的测试中手动去调用这个方法。

现在,添加以下测试方法到UITests.m:

KIF测试操作有一个可读性很好的名字,看看你能不能发现接下来会发生什么?把它当成是一个……测试!正确!当然这是一语双关。

好的,现在介绍接下来的步骤:

  1. 切换到计时器(Timer)标签栏。
  2.  在“任务名(Task Name)”可访问标签的文本框中输入“Set up a test”(记住,这个标签是你之前添加到面板中的)。点击“完成(Done)”按钮,关闭键盘。
  3. 请求帮助方法来选择第二个预设。
  4. 选择预设可以选择滑动器的颜色,所以要确保它已经更改为正确的值。

在代码最后一部分,你会发现一个技巧: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分钟。这时候,你需要好好休息一下,当你准备好之后再重新启动应用程序。

以上例子的参数是:

  • 工作时间:8分钟
  • 休息时间:2分钟
  • 重复次数:3

接下来要在KIF测试输入这些参数,然后点击“开始工作(Start Working)”按钮来启动定时器。添加以下的方法到UITests.m,直接加在你之前添加的测试之后:

因为这个测试会在test10PresetTimer(是我们设置的任务名)后立刻运行,你可以使用clearTextFromAndThenEnterText:intoViewWithAccessibilityLabel:变化的,而不是不变的
enterText:intoViewWithAccessibilityLabel:先清除任何现存的文本。

最后,这里有几个setValue:forSliderWithAccessibilityLabel:的请求。这是UISlider指定的给新变量设值 的方法。注意,这个精确性不是很好。KIF实际上是模拟触摸点击事件去设置新的值,有时像素的计算会有所偏差。但这没问题的,因为我们的手指点击也不是特 别的准!

你仅需要设置每个滚动条的值一次。多重的调用仅仅是为了剔除,所以你会看到KIF在不停地更改该值。

用Command-U运行这个测试。

剩下的事就是在UI下进行测试,并在UIStepper控制器中设置重复的次数,然后点击“开始工作(Start Working)”按钮。这个“开始工作(Start Working)”按钮很简单——你可以用tapViewWithAccessibilityLabel:去模仿一个点击。但对于UIStepper来 说,我们需要走一些弯路。

自定义点击

如下图所示,UIStepper控制器分为两部分,因此,如果你调用tapViewWithAccessibilityLabel:的时候就分不清会点击哪一块了。

1220140318105236

KIF会开始尝试点击控制器的中心。如果它是一个不可点击的区域,它会接下来尝试点击左上角、右上角、左下角、右下角这四个角。结果显示,点击边缘的中心区,介于+和-之间,会触发+,所以它会不断地增加重复次数。

但是如果你想减少次数得怎么办呢?那是有变通的方法的,例如挖掘出子视图来找到减号的按钮,另一种方法则是使用KIF的tapScreenAtPoint:这个测试动作,可以模拟点击屏幕上的任意点。

你的CGGeometry的知识如何?如何计算清楚+/-按钮在窗口中的坐标?准备好来证明你的实力没?为什么不接受这个挑战呢?这完全是可选的, 你可以直接跳到下面去查看代码和计算。但是,如果想测试你的技能,可以尝试在偷看答案之前先编码计算一下。顺便说一句,答案和完整的解释都在解决箱里。

【请注意,你准备要添加这个代码到测试中去,这个任务只是测试你的数学和CGGeometry能力】

内部解决方法:UIStepper Geometry

首先,你需要引用UIStepper:

然后你可以找到中心点:

UIStepper可以存在任何嵌入的子视图内,而你真正想要的是UIStepper的中心点相对于整个窗口的坐标,因此可以调用convertPoint:fromView:。

现在,你获取到了中心点相对于窗口的位置,你可以减少x轴的值来获取到减号的按钮,也可以增加x轴来获取加号的按钮。

如果增加或者减少UIStepper宽度的1/4,就会正好到达加号\减号按钮的中间位置。

瞧!这两个点,相当于UIStepper控制器中的加号/减号按钮的中心点。

点击屏幕的任意一点是万不得已的方法,但有时它是测试UI唯一的途径。例如,有个自定义的控制器且没实现UIAccessibility Protocol。

最终的定时器

好的,现在来到定时器测试的最后一步了。你是否开始意识到用KIF做UI测试的真正潜力么?非常好!

最后的步骤是设置重复次数,然后启动定时器。添加如下代码到UITests.m中的test20StartTimerAndWaitForFinish的末尾处:

这里介绍在最后阶段中即将会发生什么:

  1. 上面的代码是找到UIStepper的加号/减号按钮的坐标。代码的解释已经包含在上面的框框内。
  2. 调用waitForTimeInterval:增加延时,这样你可以看到步数(stepper)值的变化——否则它变化太快,我们的肉眼都看不出来了。
  3. 这个步数(stepper)的最大值是20,所以点击减号按钮20次会把值恢复到1。然后点击加号按钮2次(以最小的延时交替着)把重复数字变为期望的值,3。
  4. 一个测试步骤默认的超时时间是10秒。即使在快速的单步调试模式下,有可能20多分钟长度的“工作”也需要超过10秒钟时间的等待,所以设置一个比较宽松的超时时间——60秒。
  5. 点击“开始工作(Start Working)”按钮,此时会弹出模式视图控制器。当重复的测试次数都通过的时候,模式视图控制器就会消失。这意味着你将回到定时器视图控制器上,所以 等待“开始工作(Start Working)”按钮再次出现是有效地等待模式视图控制器消失的途径。
  6. 重设超时时间恢复为10秒来清除所有数据。

一旦你保存文件,你会发现有一个小菱形图标出现在方法声明的旁边:

1320140318105251

这是一个运行单个测试的按钮。因此,你可以在这个测试环境下只运行这个方法,而不需要运行整套测试用例。简单利落!点击菱形按钮去运行这个测试,你 会看到模拟器启动,并运行测试用例。只要坐在一旁观看它输入任务名,滑动滚动条,以及定时器不停地流走。无需耗吹灰之力,你便能测试UI了。

成功了!现在你可以成功设置测试,帮助你完成一系列UI控制器的操作。

如果你点击菱形按钮跑一个简单的测试,只能跑一个简单的方法而且没有办法在开头调用beforeAll。如果你的测试是依赖于beforeAll,你仍然需要跑整套完整的测试用例。

提前结束

“去工作吧!(Get to Work!)”模式视图控制器里有一个放弃(Give Up)的按钮可以让用户取消这次定时器的周期。你仍可以衡量这已经工作的几分钟,即使你一开始突然中断这个测试。这个数据仍然会被记录到历史上,但会做上记号表示整个周期没有跑完。

但你可以不用相信我的话,你可以自己去尝试一下。只需做一些类似前面测试的东西,设置定时器的参数,点击开始工作(Start Working)按钮,然后点击放弃(Give Up)按钮。

不要立即点击“放弃(Give Up)”按钮——定时器需要过一会儿,应用程序才开始创建历史记录。所以,你可以将鼠标移到测试上,手动去停掉这个测试,或者,你可以设定它停在你选择的 时间和位置。不管怎么样,如果你喜欢编写代码去帮你处理各种琐事,就试试吧!你知道怎么添加一些延时在”开始工作(Start Working)”和“放弃工作(Give Up)”之间吗?

内部解决:添加延时

你可以调用waitForTimeInterval:在你的测试里增加延时。

把下面的测试方法添加到UITests.m中你写的其他测试的下面:

在确保你处在正确的选项卡之后,设置任务名然后选择好预设。然后启动定时器,等待3秒钟之后点击放弃。

方法的最后一行是等待模式视图控制器消失以及返回主界面。请记住,默认的超时时间是10秒,实际上不需要这么长时间——点击“放弃(Give Up)”按钮,模式视图控制器就会立即消失。

在之前的测试中,你使用了类方法setDefaultTimeout:为所有测试的操作设置全局的超时时间。这里,你可以调用usingTimeout:仅为这一步设置自定义的超时时间。

保存文件并点击测试前面的菱形按钮,仅仅跑这一个测试。当定时器开始的时候,你可以发现在它放弃并回到主界面之前会有3秒钟的等待。

1420140318105317

历史和拖动

之前还没有重视过历史选项卡,现在轮到它出场了。如果你是按照练习一步一步来进行操作的,那么你至少会有一条历史记录。构建并运行该应用程序,然后切换到历史选项卡。

1520140318105333

历史视图实现了最新的iOS7删除手势——当你把整行往左边拖动,就会有个“删除”按钮出现。这便是你下一项测试项目了!这个要求至少有一条历史记 录,所以你独立地跑这个测试的时候需要非常注意。你必须得保证有历史记录在上面,否则什么都测不了!为了安全起见,你最好完整地运行整一套测试,因为前面 的测试会创建许多历史记录供你享用。记住,测试的运行顺序是根据字母顺序排列的,所以你可以放心前面的测试会创建一些历史记录的视图。

如果你看一下在HistoryViewController.m的 tableView:cellForRowAtIndexPath:,你会发现每一格都能得到一个可访问的标签类似“第3行第0个”,这有助于KIF去找 到这一行。但在真实环境下这种标签的可访问性不大好,所以我们真正在跑应用程序的时候都不这么用。在这个实例工程中,我们只用了#ifdef来调试。如果 在应用程序中用这个技术,你也可以做类似的事情。请确保在发布构建的时候,为一些有用的用户辅助功能设置可访问的标签。

现在打开UITests.m并添加如下的测试方法:

现在介绍上面方法的作用:

  1. 切换到“历史(History)”标签栏。
  2. 引用表视图并跟踪获取总共存在多少行。如果表中总行数少于1行,这个测试会失败。
  3. 往左拖动表视图的单元格。当删除按钮出现的时候,点击它。
  4. 当删除一个单元格的时候,表视图会有一个动态的效果,所以在进行下一步前添加一个短暂的延时。再次检查表视图的行数,会比之前的行数少一行。

Command-U运行整套测试中的其余测试,看看其测试的顺序。

拖动并删除

现在,你的测试覆盖了整个应用程序最基本的流程了——从最开始的数据重置到多次运行定时器,再到验证和删除历史记录。

如果你要继续开发这个应用程序,这些测试是一个很好的基线去保证界面没有回退。这意味着你只需要在主界面变化的时候更新你的测试——测试用例就像是你应用程序的规格说明,他们需要更新到最新版本才是有用的。

何去何从?

到目前为止,你已了解了KIF,以及开始思考如何利用这种高效的功能测试来测试你自己的应用程序。刚开始添加KIF到你的工程中的时候,可以查看该文档。你可以手动添加KIF或者使用非常方便的CocoaPods依赖管理。

KIF最酷的地方是它是一个开源的项目,且有许多新功能还在不断开发中。例如,在写这篇教程的时候,下一个版本将会提供截屏的功能并且能够保存下 来。这意味着当你跑完测试之后,可以在你空闲时通过截图来查看整个过程中的关键点。难道这不是比鼠标移动上去并用肉眼观察KIF点击和拖动整个过程好上千 倍万倍么?KIF变得越来越好了,所以学习如何使用它,对于自己来说是一个很好的投资。

最后,由于KIF测试用例是继承了OCUnit,并使用了标准的Xcode5测试框架,你可以使用持续集成来跑这个测试。当你在忙着别的事情的时候,就拥有了一个能够像人的手指一样准点触控的机器人去测试你的应用程序。太棒了!

你可以在这里下载最终工程代码。如果你还有任何疑问和评论,可以加入下面的论坛讨论。测试愉快!

收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部