构建iOS持续集成平台
 
作者 刘先宁,火龙果软件    发布于 2013-11-11
 

(一)——自动化构建和依赖管理

2000年Matin Fowler发表文章Continuous Integration【1】;2007年,Paul Duvall, Steve Matyas和 Andrew Glover合著的《Continuous Integration:Improving Software Quality and Reducing Risk》 【2】出版发行,该书获得了2008年的图灵大奖。持续集成理念经过10多年的发展,已经成为了业界的标准。在Java, Ruby的世界已经诞生了非常成熟的持续集成工具和实践,而对于iOS领域来说,因为技术本身相对比较年轻和苹果与生俱来的封闭思想,在持续集成方面的发展相对滞后一些,但是,随着越来越多的iOS开发者的涌入,以及各个互联网巨头加大对iOS开发的投入,诞生了一大批非常好用的持续集成工具和服务,本文的目的就是介绍一下如何有效的利用这些类库,服务快速构建一个iOS开发环境下的持续集成平台。

自动化构建

在MartinFowler的文章[1]中关于自动化的构建定义如下:

Anyone should be able to bring in a virgin machine, check the sources 
out of the repository, issue a single command, and have a running
system on their machine.

因此,自动化构建的的首要前提是有一个支持自动化构建的命令行工具,可以让开发人员可以通过一个简单的命令运行当前项目。

命令行工具

自动化构建的命令行工具比持续集成的概念要诞生得早很多,几十年前,Unix世界就已经有了Make,而Java世界有Ant,Maven,以及当前最流行的Gradle,.Net世界则有Nant和MSBuild。作为以GUI和命令行操作结合的完美性著称的苹果公司来说,当然也不会忘记为自己的封闭的iOS系统提供开发环境下命令行编译工具:xcodebuild【3】

xcodebuild

在介绍xcodebuild之前,需要先弄清楚一些在XCode环境下的一些概念【4】:

Workspace:简单来说,Workspace就是一个容器,在该容器中可以存放多个你创建的Xcode Project, 以及其他的项目中需要使用到的文件。使用Workspace的好处有,1),扩展项目的可视域,即可以在多个项目之间跳转,重构,一个项目可以使用另一个项目的输出。Workspace会负责各个Project之间提供各种相互依赖的关系;2),多个项目之间共享Build目录。

Project:指一个项目,该项目会负责管理生成一个或者多个软件产品的全部文件和配置,一个Project可以包含多个Target。

Target:一个Target是指在一个Project中构建的一个产品,它包含了构建该产品的所有文件,以及如何构建该产品的配置。

Scheme:一个定义好构建过程的Target成为一个Scheme。可在Scheme中定义的Target的构建过程有

Build/Run/Test/Profile/Analyze/Archive

BuildSetting:配置产品的Build设置,比方说,使用哪个Architectures?使用哪个版本的SDK?。在Xcode Project中,有Project级别的Build Setting,也有Target级别的Build Setting。Build一个产品时一定是针对某个Target的,因此,XCode中总是优先选择Target的Build Setting,如果Target没有配置,则会使用Project的Build Setting。

弄清楚上面的这些概念之后,xcodebuild就很好理解了,官网上对其作用的描述如下:

xcodebuild builds one or more targets contained in an Xcode 
project, or builds a scheme contained in an Xcode workspace or
Xcode project.

xcodebuild就是用了构建产品的命令行工具,其用法可以归结为3个部分:

1.可构建的对象

2.构建行为

3.一些其他的辅助命令

可以构建的对象有,默认情况下会运行project下的第一个target:

1.workspace:必须和“-scheme”一起使用,构建该workspace下的一个scheme。

2.project:当根目录下有多个Project的时候,必须使用“-project”指定project,然后会运行

3.target:构建某个Target

4.scheme:和“-workspace”一起使用,指定构建的scheme。

5.……

构建行为包括:

1.clean:清除build目录下的

2.build: 构建

3.test: 测试某个scheme,必须和"-scheme"一起使用

4.archive:打包,必须和“-scheme”一起使用

5.……

辅助命令包括:

1.-sdk:指定构建使用的SDK

2.-list:列出当前项目下所有的Target和scheme。

3.-version:版本信息

4.…...

关于xcodebuild更多详细的命令行请参见:https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html

下图是使用XcodeBuild运行一个scheme的build的结果:

了解了xcodebuild的用法之后,接下来分析一下xcodebuild的主要缺陷:

1.从上图直接可以得到的感觉,其脚本输出的可读性极差,

2.只能要么完整的运行一个target或者scheme,要么全部不运行。不能指定运行Target中特定的测试。

3.最令人发指的是,XCode 4中的xcodebuild居然不支持iOSUnitTest的Target【5】,当我尝试运行一个iOS App的测试target时,得到如下的错误:

对于上面提到的缺陷,Facebook给出了他们的解决方案:xctool【6】

xctool

xctool在 其主页直接表明了其目的:

xctool is a replacement for Apple's xcodebuild that makes it easier  to build and
test iOS and Mac products. It's especially helpful for continuous integration.

其作用是替代xcodebuild,目的是让构建和测试更加容易,更好的支持持续集成。从个人感受来看,它的确成功取代了xcodebuild。但是xctool说到底只是对xcodebuild的一个封装,只是提供了更加丰富的build指令,因此,使用xctool的前提是xcodebuild已经存在,且能正常工作。

安装

xctool的安装非常简单,只需要clone xctool的repository到项目根目录就可以使用, 如果你的机器上安装有Homebrew,可以通过“brew install xctool”命令直接安装。(注意:使用xctool前一定要首先确认xcodebuild已安装且能正确工作)。

用法

关于xctool的用法就更加人性化了,几乎可以重用所有的xcodebuild的指令,配置。只需要注意一下几点:

1.xctool不支持target构建,只能使用scheme构建。

2.支持“-only”指令运行指定的测试。

3.支持多种格式的build报告。

例子:

path/to/xctool.sh 
-workspaceYourWorkspace.xcworkspace
-schemeYourScheme
test -only SomeTestTarget:SomeTestClass/testSomeMethod

下图是我使用xctool运行test的效果:

常见问题:

No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=armv7 armv7s).

解决方法:到Project Setting中,把"Build Active Architecture Only"设置为NO

Code Sign error: A valid provisioning profile matching the application's Identifier 'dk.muncken.MyApp' could not be found

解决方法:通过“-sdkiphonesimulator”指定SDK,从而能够使用符合iOS约定的application Identifier。

依赖管理

选定了命令行工具之后, 接下来可以考虑下依赖管理的问题了。我到现在还记得几年前,刚从Ant转到使用Maven的那种爽快的感觉。后来,进入Ruby的世界,其与生俱来的Gem管理系统,也让其依赖管理变得极其简单。 对于iOS平台来说,在做项目时,经常需要使用到各种各样的第三方Framework,这同样需要一个爽快的依赖管理系统,不然的话,各位可以想象一下重复的下载Framework文件,拖入各个Target的Build Phase的Link Binary With Libraries中的场景。这种重复的劳动对于“懒惰”的程序员来说,是很难接受的,于是,活跃的社区开发者们提供了这样的一个工具:Cocoapods【7】

Cocoapods开始于2011年8月12日,经过2年多的发展,现在已经超过2500次提交,并且持续保持活跃更新,目前已成为iOS领域最流行的第三方依赖管理工具。从技术层面来说,其是一个Ruby Gem,从功能层面来说,其是一个iOS平台下的依赖管理工具,为iOS项目开发提供了类似于在Ruby世界使用Gem的依赖管理体验。

安装

前面提到cocoapods本质上是一个Ruby Gem,因此,其使用前提首先是Ruby开发环境。庆幸的是,Mac下都自带Ruby。这样,只需要简单的2条命令,就可以把cocoapods安装好:

$ [sudo] gem install cocoapods
$ pod setup

用法

cocoapods的使用方式和使用Ruby Gem非常相似,首先需要在项目根目录下创建文件Podfile,在Podfile中,开发人员只需要按照规则配置好如下内容就好:

1.项目支持的平台,版本(iOS/OSX)

2.每个target的第三方依赖

例子:

platform :ios, '6.0'
inhibit_all_warnings!
xcodeproj `MyProject`
pod 'ObjectiveSugar', '~> 0.5'
target :test do
pod 'OCMock', '~> 2.0.1'
end
post_install do |installer|
installer.project.targets.each do |target|
puts "#{target.name}"
end
end

修改好配置文件之后,只需要简单的使用“pod install”即可安装好所有的依赖,执行该命令之后,在项目跟目录下会出现“.xcworkspace”和“Pods”两个目录:

接下来,开发者需要使用xcworkspace打开项目而不是使用xcodeproject,打开项目之后,在项目目录下除了自己的project以外,还可以看到一个叫做Pods的项目,该项目会为每一个依赖创建一个target:

在Podfile中,还可以指定依赖专属于某个Target,

target :CocoaPodsTest do
pod 'OCMock', '~> 2.0.1'
pod 'OCHamcrest'
end

如果你记不清楚某个依赖库的名称,可以使用“pod search <name>”模糊搜索依赖库中的相似库,另外,如果你想使用的库在cocoapods的中央库中找不到,那么,你可以考虑为开源社区做做贡献,把你觉得好用的库添加到中央库中,Cocoapods的官网上有具体的步骤【8】

原理

CocoaPods的原理思想基本上来自于Jonah Williams的博客“Using Open Source Static Libraries in Xcode 4”【9】,当使用“pod install”安装文件时,cocoapods做了如下这些事:

1.创建或者更新当前的workspace

2.创建一个新的项目来存放静态库

3.把静态库会编译生成的libpods.a文件配置到target的build phase的link with libraries中

4.在依赖项目中创建*.xcconfig文件,指定在编译时的一些参数和依赖

5.添加一个新的名为“Copy Pods Resource”的Build Phase,该build phase会使用"${SRCROOT}/Pods/Pods-CocoaPodsTest-resources.sh"把Pods下的资源文件拷贝到app bundle下。

注意事项

当使用xctool作为命令行工具构建项目时,使用cocoapods管理依赖时,需要做一些额外的配置:

1.编辑Scheme,把pods静态库项目作为显式的依赖添加到项目的build中,

2.把pods依赖项目拖动到本来的项目之上,表示先编译pods静态库项目,再编译自己的项目。

(二)——测试框架

测试框架

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

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

Unit Test

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

 优点
缺点
OCUnit与Xcode无缝集成, 快捷键,Scheme配置都非常方便1. 只能一次运行整个测试,不能灵活的运行某个测试集; 2.测试结果输出的可读性不好,不容易找到失败的测试
GHUnit1.自带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的对比,感兴趣的同学可以跳过去看看。

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做测试时,开发者必须掌握两件事:

1.如何找到界面上的一个UI元素

2.如何指定针对一个UI元素的操作

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

1.name

2.value

3.elements

4.parent

5.…

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

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元素和操作,比方说:

1.点击按钮,例: ***.buttons[“add”].tap()

2.输入文本, 例:***.textfields[0].setValue(“new”)

3.滚动屏幕,例:***.scrollToElementWithPredicate(“name begin with ’test’”)

4.……

关于使用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测试脚本

指定目标设备,构建被测应用,该应用会被安装到指定的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

启动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种格式,以支持多种运行方式:

1.scriptName.fm:用于支持在Fone Monkey的窗口中运行测试

2.scriptName.m:用于和Xcode的OCUnit test 集成,可以以OCUnit的测试用例的形式运行UI测试,这就让UI具备了命令行运行和与CI集成的能力。

3.scriptName.js:UIAutomation的格式,用于支持在Instruments中,以UIAutomation的形式运行测试。

(三)——CI服务器与自动化部署

CI服务器

写到这儿,对于iOS开发者来说,需要准备好:

一个比较容易获取的源代码仓库(包含源代码)
一套自动化构建脚本
一系列围绕构建的可执行测试
接下来就需要一个CI服务器来根据源代码的变更触发构建,监控测试结果。目前,业界比较流行的,支持iOS构建的CI服务器有Travis CI和Jenkins

Travis CI

Travis CI【20】是一个免费的云服务平台,主要功能就是为开源社区提供免费的CI服务,对于商业用户可以使用Travis Pro版本,其基本上支持所有目前主流的语言,Object-C自然也在其中。但是,Travis CI只支持github极大的限制了其应用场景。但是也由于其只支持github,其把和github的集成做到了极致的平滑,易用,因此,对于本就把github作为代码托管平台的项目来说,Travis CI可以做为第一选择。

使用Travis CI只需要简单的2步即可为你托管在github的项目增加一个CI服务器

第一步:在项目的根目录下创建travis CI配置文件“.travis.yml”,在配置文件指定语言,环境要求,构建脚本等

language: objective-c
before_install:
- brew update
- brew install xctool
script: xctool -project LighterViewControllerDemo.xcodeproj -scheme
LighterViewControllerDemo -sdkiphonesimulator test

第二步:使用github账号登陆Travis CI,在账户的repositories开启该项目的自动构建功能,该设置会在github上该项目repository中开启对Travis CI的Service Hook

下图就是我的一个iOS项目在Travis CI的构建记录:

除此之外,Travis CI还非常贴心的提供了一个指示灯,可以让开发者在自己的repository展示构建状态。使用指示灯的方法很简单,只需要在repository的README.md中添加下面这行代码就行了。

[![Build Status](https://travis-ci.org/xianlinbox/iOSCIDemo.png)](https://travis-ci.
org/xianlinbox/iOSCIDemo)

效果如下:

Jenkins

Jenkins【21】经过多年的发展,其活跃的社区和丰富的插件让其成为了业界最受欢迎的CI服务器。通过使用Xcode插件,可以非常方便在Jenkins中运行iOS项目的构建脚本。

安装

Jenkins的安装非常简单,尤其是在Mac环境下使用Homebrew安装,只需要简单的使用“brew install jenkins”,即可成功安装,这个安装过程做的事情非常简单,就是把对应版本的jenkins.war文件下载到对应的目录“/usr/local/Cellar/jenkins/1.524/libexec/”。因此,如果没有Homebrew,可以直接到官网下载安装文件,自己安装。

配置安装完之后,只需要使用“java -jar */jenkins.war”即可启动Jenkins,开发者可以自己创建一个bash alias简化输入,在用户目录下的.bashrc文件中添加如下代码

aliasjenkins='java -jar /Applications/Jenkins/jenkins.war'

启动Jenkins之后,可以通过浏览器访问http://localhost:8080查看Jenkins界面。

然后,开发者可以到Manage Plugins界面,安装需要的插件:Xcode Plugin,Git Plugin,Github Plugin(我使用git做源代码管理),Cocoapods Plugin。安装好插件之后,需要重启Jenkins,一切就绪之后,开始配置自己的iOS构建过程。

在Jenkins中配置一个iOS Job的步骤如下:

1.新建Job,Job类型选择“Build a free-style software project”。

2.配置代码仓库,

3.点击“Add build step”添加构建步骤,如果已安装Xcode插件,则可以在Step类型中看到Xcode选项:

选择Xcode,可以看到Xcode构建step的所有配置选项:

4.点击“Add build step”添加测试步骤,选择“Execute shell”选项,然后,添加脚本,执行测试并生成期望的Report, 可以重复本步骤添加“Acceptaince Test”等构建步骤。

path-to/xctool.sh 
-workspaceAudioDemo.xcworkspace -scheme AudioDemo -sdkiphonesimulator
-reporter junit:test-reports/junit-report.xml clean test

5.点击“Add post-build action”添加一个新的步骤,选择“publish Junit test result report”,把测试报告展示在Jenkins中。

配置好之后,可以点击Build Now运行Job,下图是在Jenkins中运行iOS构建 Job的结果图:

开发者可以点击每次的构建,查看具体的构建信息及测试结果:

常见问题

启动Jenkins提示运行“java”命令需要X11支持,

解决方法:这是因为在OS X Mountain Lion系统中不再内置X11,当需要使用X11时,可通过安装XQuartz project得到支持,具体信息:http://support.apple.com/kb/HT5293

自动化部署

这儿的想谈的“部署”不是传统意义上的直接部署到产品环境的部署,而是指如何把最新版本的应用快速的部署到测试用户的机器上以收集反馈,或者做一些探索性的测试。

在我写第一个iOS应用的时候,我想把应用安装到多个机器上测试的时候,需要非常繁琐的步骤:

1.需要申请到苹果开发者账号,获得开发者证书。

2.需要在苹果的开发者网站上注册我想使用的设备。

3.使用开发者证书打包应用,使用Ad-HOC部署模式,生成ipa文件。

4.通过ipa文件把应用安装到iTunes上。

5.通过iTunes把应用同步到多台测试机器上。

如果是测试机器在多个地理位置的时候,还需要把ipa文件发送到对应的地点,每个地点都需要重复的做第4,5步。 这样一个繁琐,且低效的过程让开发者非常痛苦,直到TestFlight的出现。

TestFlight

TestFlight【22】就是一个专门解决上面提到的痛点的云服务方案,它可以帮助开发者:

1.轻松采集测试用户的UDID和iOS 版本、硬件版本,并发送给开发者。

2.实时反馈应用是否成功安装到测试机器

3.轻松部署最新版本应用到测试用机上。

4.开发者可以灵活选择部署哪个版本到哪部分测试机器上。

使用使用Test Flight服务非常简单,只需要到Test Flight注册一个账号。然后把链接发送给测试设备,测试设备只要打开该链接,并授权给Test Flight,在Test Flight的设备中心就可以看到这些设备。

而测试设备上,则会多一个Test Flight的应用。

当应用有了新的测试包之后,只需要将IPA上传到TestFlight网站,然后勾选合适的测试用户或者合适的设备,点击Update & Notify。

TestFlight会向对应的测试设备发送更新通知,测试用户只需在测试设备上打开TestFlight应用,点击Install,TestFlight就会自动将新版本的IPA文件安装到测试设备上。

另外,TestFlight还提供了Upload API,这样就等于提供了和CI服务器集成的能力。其Upload API非常简单,就是一个简单的,带有指定参数的HTTP POST请求,具体细节可参考官网:https://www.testflightapp.com/api/doc/。

在 CI服务器中,开发者可以在构建过程中,添加步骤通过upload API把通过测试的ipa文件自动上传到TestFlight上,从而达到持续部署的目的。而像Jenkins这样有丰富插件机制的CI服务器, 活跃的社区开发者们早为其开发了十分便于使用的TestFlight的插件。

在Jenkins中使用TestFlight插件也非常简单,安装好插件,重启Jenkins,然后在项目的构建过程配置页面的Post-build Actions中,点击add post-build action,可以看到Upload to Testflight选项:

选择之后,可以在页面上看到TestFlight的配置项:

为了增强安全性,该插件把Token的设置移到了Jenkins的Global Setting界面:

配置好Token Pair之后,在在TestFlight的配置项 上选择相应的pair即可,设置想要的参数,保存即可。

你如果不喜欢使用插件或者说使用的其它CI服务器的话,可以通过添加一个Execute shell步骤,直接通过代码上传构建好的ipa文件:

curl http://testflightapp.com/api/builds.json      
-F file=@testflightapp.ipa
-F dsym=@testflightapp.app.dSYM.zip
-F api_token='your_api_token'
-F team_token='your_team_token'
-F notes='This build was uploaded via the upload API'
-F notify=True
-F distribution_lists='Internal, QA'

结语

《持续集成》一书中引用了Javaranch.com的创始人Kathy Sierra的一句话:

There's a big difference between saying, "Eat an apple a day" and actually eating the apple

正所谓知易行难,您几乎很难听到开发者说:“持续集成毫无价值”,但是,构建一个持续集成平台并非易事,希望本文中介绍的iOS应用的构建过程,以及在构建过程的各个阶段可以使用的一些优秀的类库,服务,能够让iOS开发者们在想搭建一个持续集成平台时有所参考,从而能够更加坚定,且容易的为自己的项目搭建一个持续集成平台。