构建 iOS 持续集成平台(二)——测试框架

阅读数:9635 2013 年 9 月 17 日

话题:持续集成iOS语言 & 开发架构

测试框架

有了自动化构建和依赖管理之后,开发者可以很轻松的在命令行构建整个项目,但是,作为持续集成平台来说,最重要的还是测试,持续集成最大的好处在于能够尽早发现问题,降低解决问题的成本。而发现问题的手段主要就是测试。在 Martin Fowler 的 Test Pyramid【10】一文中论述了测试金子塔的概念,测试金字塔的概念来自Mike Cohn,在他的书Succeeding With Agile中有详细描述:测试金字塔最底层是单元测试,然后是业务逻辑测试,如果更细化一点的话,可以分为把完整的测试策略分为如下的层级:

作为持续集成平台,能自动化的测试层级越多,平台就能产生越大的价值。

Unit Test

目前,在 iOS 领域, 最流行的 Unit 测试框架有 2 个:OCUnit【11】和 GHunit【12】,这两个框架各有其优缺点:

 

优点

缺点

OCUnit

与 Xcode 无缝集成, 快捷键,Scheme 配置都非常方便

1. 只能一次运行整个测试,不能灵活的运行某个测试集 ; 2. 测试结果输出的可读性不好,不容易找到失败的测试

GHUnit

1. 自带 GUI,测试结果清晰可见;2. 可以灵活的运行指定的测试;3. 开源项目

1. 需开发者安装,配置略显复杂;2. 对命令行运行测试的支持不是很好,

OCUnit 的运行结果会通过弹窗直接告诉开发者,运行的细节信息则会打印在 Xcode 的输出窗口中:

GHUnit 的运行结果则全部显示在自己的应用界面中,开发者可以在应用中查看所有的信息,以及做运行测试等各种各样的操作。

关于如何使用 OCUnit 和 GHUnit, InfoQ 上有高嘉峻的文章《iOS 开发中的单元测试》(http://www.infoq.com/cn/articles/ios-unit-test-1)有详细的介绍,我就不再这儿重复叙述了。

如果单从单元测试框架来看,个人更喜欢 GHUnit 测试结果的可读性和运行测试的灵活性,但是,随着 Facebook 的 xctool 的发布,OCUnit 华丽丽的逆袭了,因为 xctool 帮助 OCUnit 把运行测试的灵活性和测试结果的可读性这两块短板给补齐了,再加上其和 Xcode 的集成优势以及通过命令行运行的便捷性,让其成为持续集成平台的 Unit 测试框架的首选。

在 Java 程序员的心中,Junit 和 Hamcrest 永远是一体的,Hamcrest 为 junit 提供了非常丰富的断言机制,极大的增强了测试的可读性。越来越活跃的 iOS 开发社区,当然不会让 Object-C 的世界缺失这样一个优秀的框架,于是 OCHamcrest【13】诞生了。

在测试项目中使用 OCHamcrest 非常简单,尤其是使用了 cocoapods 管理依赖的项目。只需要在 Podfile 文件中加上:

target :<TestTargetName> do
    ...
pod 'OCHamcrest'
end

然后,运行“pod install”命令安装 Hamcrest 到测试 Target,安装好之后,为了在测试类中使用 OCHamcrest 的断言。还需要在测试类的头文件中加入如下代码:

#define HC_SHORTHAND
#import<OCHamcrest/OCHamcrest.h>

开发者可以把这段代码加入 <TestTargetName>-prefix.pch 中,这样所有的测试类就都可以使用 OCHamcrest 的断言了。在前面提到的高嘉峻的文章中的第二部分更加详细的讲解了 OCHamcrest 的断言,以及其和另一个断言框架 Expecta 的对比,感兴趣的同学可以跳过去看看(http://www.infoq.com/cn/articles/Matching-Engine-Enliven-Assertion-2?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent_articles_clk )。

Component Test & Integration Test

在开发手机应用时,总难免会和其他的系统集成,尤其当开发的应用是某个系统的手机客户端时,这样就涉及到很多第三方 API 的集成点需要测试,在成熟的 Java 世界中,诞生了 EasyMock,Mockito,moco 等针对这种集成点的测试工具。同样的,活跃的社区力量正一步一步的在让 Object-C 世界成熟,OCMock【14】诞生。

OCMock

有了 cocoaPods,新加框架变得非常容易,基本上就是“哪里没有加哪里”的节奏,添加 OCMock 框架,只需要在 Podfile 文件中加上:

target :<TestTargetName> do
    ...
pod 'OCMock', '~> 2.0.1'
end

然后,运行“pod install”命令安装 OCMock 到测试 Target,同样的,需要把 OCmock 的头文件添加 <TestTargetName>-prefix.pch 文件中

#import<OCMock/OCMock.h>

下面是一个简单的使用 OCMock 的例子,更多的用法请参考 OCMock 的官网:http://ocmock.org/features/:

- (void)testSimpeMockPass{
idmockObject = [OCMockObjectmockForClass:NSString.class];
    [[[mockObject stub] andReturn:@"test"] lowercaseString];

NSString * returnValue = [mockObjectlowercaseString];
assertThat(returnValue, equalTo(@"test"));
}

moco

moco【15】以其对系统集成点测试的贡献荣获了 2013 年的“Duke's Choice Award”奖,虽然其以 Java 写成,主要的生态系统也是围绕 Java 打造,但是,其 Standalone 模式可以非常方便的构造一个开发者期望的服务器,这对于 Mobile 领域的集成点测试来说,本就是一个非常好的 Mock 服务器的工具。Moco 有如下的特点:

  • 易于配置,使用:一个 Json 配置文件,然后“java -jar moco-runner--standalone.jar -p 8080 ***.json”就可以启动一个 Mock 服务器。该服务器的所有行为都在配置文件里。如果你想添加,修改服务器行为,只需要修改一下配置文件,然后重新启动该服务器就行了。
  • 配置文件可读性好,使用 Json 格式的配置文件,对绝大多数开发者来说都可以很容易理解。
  • 支持模拟客户端需要的所有 http 操作,moco 实现了针对请求 Content、URI、Query Parameter、Http Method、Header、Xpath 的模拟。对响应的格式支持有 Content、Status Code、Header、URL、甚至支持 Sequence 请求,即根据对同一请求的调用次数返回不同的结果。
  • 完全开源,代码不多也比较易懂,如果没有覆盖到我们的场景,完全可以在该项目基础上实现一个自己的 Mock 服务器 。

熊节在 infoQ 上发表的《企业系统集成点测试策略》【16】一文中,详细的论述了在企业系统中,moco 对测试系统集成点的 帮助。这些论点在 Mobile 开发领域同样适用,因此合理的使用 moco 可以帮助 iOS 开发者更加容易的构建一个稳固的持续集成平台。

System Test

对于 iOS 的系统(UI)测试来说,比较知名的工具有 UIAutomation【17】和 FonMonkey【18】。

UIAutomation

UIAutomation 是随着 iOS SDK 4.0 引入,帮助开发者在真实设备和模拟器上执行自动化的 UI 测试。其本质上是一个 Javascript 的类库,通过 界面上的标签和值的访问性来获得 UI 元素,完成相应的交互操作,从而达到测试的目的,类似于 Web 世界的 Selenium。

通过上面的描述,可以得知,使用 UIAutomation 做测试时,开发者必须掌握两件事:

  • 如何找到界面上的一个 UI 元素
  • 如何指定针对一个 UI 元素的操作

在 UIAutomation 中,界面就是由一堆 UI 元素构建的层级结构,所有 UI 元素都继承对象 UIAElement ,该对象提供了每个 UI 元素必须具备的一些属性:

  • name
  • value
  • elements
  • parent

而整个界面的层级结构如下:

Target(设备级别的 UI,用于支持晃动,屏幕方向变动等操作)
    Application(设备上的应用,比方说 Status Bar,keyboard 等)
      Main window(应用的界面,比方说导航条)
        View(界面下的 View,比方说 UITableView)
           Element(View 下的一个元素)
              Child element(元素下的一个子元素)

下面是一个访问到 Child element 的例子:

UIATarget.localTarget().HamcrestDemo().tableViews()[0].cells()[0].elements()

开发者还可以通过“UIATarget.localTarget().logElementTree()”在控制台打印出该 target 下所有的的 elements。

找到 UI 元素之后,开发者可以基于该 UI 元素做期望的操作,UIAutomation 作为原生的 UI 测试框架,基本上支持 iOS 上的所有 UI 元素和操作,比方说:

  • 点击按钮,例: ***.buttons[“add”].tap()
  • 输入文本, 例:***.textfields[0].setValue(“new”)
  • 滚动屏幕,例:***.scrollToElementWithPredicate(“name begin with ’test’”)
  • ……

关于使用 UIAutomation 做 UI 测试,推荐大家一定要看一下 2010 的 WWDC 的 Session 306:Automating User Interface Testing with Instruments【19】。 另外,这儿还有一篇很好的博客,详细的讲解了如何使用 UIAutomation 做 UI 自动化测试:http://blog.manbolo.com/2012/04/08/ios-automated-tests-with-uiautomation

Apple 通过 Instruments 为 UIAutomation 测试用例的命令行运行提供了支持,这样就为 UIAutomation 和 CI 服务器的集成提供了便利。开发者可以通过如下的步骤在命令行中运行 UIAutomation 测试脚本

  1. 指定目标设备,构建被测应用,该应用会被安装到指定的 DSTROOT 目录下

    xcodebuild
    -project "/Users/twer/Documents/xcodeworkspace/AudioDemo/AudioDemo.xcodeproj" 
    -schemeAudioDemo
    -sdk iphonesimulator6.1 
    -configuration Release SYMROOT="/Users/twer/Documents/xcodeworkspace/
    AudioDemo/build" DSTROOT="/Users/twer/Documents/xcodeworkspace/AudioDemo/
    build" TARGETED_DEVICE_FAMILY="1" 
    install
    
  2. 启动 Instruments,基于第一步生成的应用运行 UIAutomation 测试

    instruments
    -t  "/Applications/Xcode.app/Contents/Applications/Instruments.app/
    Contents/PlugIns/AutomationInstrument.bundle/Contents/Resources/
    Automation.tracetemplate" "/Users/twer/Documents/xcodeworkspace/AudioDemo
    /build/Applications/TestExample.app"
    -e UIASCRIPT <absolute_path_to_the_test_file>
    

为了更好的展示测试效果以及与 CI 服务器集成,活跃的社区开发者们还尝试把 UIAutomation 和 Jasmine 集成: https://github.com/shaune/jasmine-ios-acceptance-tests

UIAutomation 因其原生支持,并且通过和 Instruments 的绝佳配合,开发者可以非常方便的使用录制操作自动生成测试脚本,赢得了很多开发者的支持,但是因苹果公司的基因,其系统非常封闭,导致开发者难以扩展,于是活跃的社区开发者们开始制造自己的轮子,Fone Monkey 就是其中的一个优秀成果。

Fone Monkey

Fone Monkey 是由 Gorilla Logic 公司创建并维护的一个 iOS 自动化测试工具,其功能和 UIAutomation 差不多,但是由于其开源特性,极大的解放了活跃开发者的生产力,开发者可以很容易的根据自身需要扩展其功能。

Fone Monkey 的安装虽然简单,但是比 UIAutomation 的原生支持来说,也算是一大劣势了,具体的安装过程可以参考:http://www.gorillalogic.com/fonemonkey-ios/fonemonkey-setup-guide/add-fonemonkey-your-xcode-project

Fone Monkey 的使用方式主要就是录制 / 回放,也可以把录制好的测试用例保存为脚本以其他方式运行。安装好 Fone Monkey 启动测试以后,应用界面会有点变化:

开发者通过点击 Record 按钮录制操作,点击 Play 按钮播放之前录制的操作,点击 More 按钮可以添加一些针对元素的验证, 这样就形成了一个测试用例。

在 Fone Monkey 中录制的测试用例可以保存为 3 种格式,以支持多种运行方式:

  • scriptName.fm:用于支持在 Fone Monkey 的窗口中运行测试
  • scriptName.m:用于和 Xcode 的 OCUnit test 集成,可以以 OCUnit 的测试用例的形式运行 UI 测试,这就让 UI 具备了命令行运行和与 CI 集成的能力。
  • scriptName.js:UIAutomation 的格式,用于支持在 Instruments 中,以 UIAutomation 的形式运行测试。