持续集成那点事

开发中,我们经常遇到一些奇怪的问题,比如本地可以编译成功的代码但是同事们更新代码后编译出错,或者在项目有多个 Target 的时候,资源文件只添加到了当前的 Target,另外一个 Target 这个时候是不能正常编译的,再比如写的工具类,被同事改了,或者自己有改动,很多地方用到了,怎么保证这个类的行为没有发生变化而影响到项目中的其他模块呢?诸如此类。

那么这些问题原因在哪,可不可以避免呢?当然是可以避免的,如果代码有新的改动,提交到版本库中的时候,有一个人帮我们检查必要事项,然后做做测试不就好了,这个当然是可以的,前提是老板同意专门招一个这样的人。

引起各种奇怪问题的原因有很多,比如开发环境比较复杂,不干净,IDE 的 bug (Xcode 的各种奇怪表现就是一个很大的坑),提交前有一些必要的检查需要做,但是开发的时候因为各种原因没做,这些机械重复的事情我们可以找一个工具来帮我们完成,而且这个工具跑在一个专门的服务器上,该服务器的环境相对干净,可以运行一些自动化的操作,如自动编译,代码检查,测试等环节,那么这种东西,就是接下来讲的「持续集成」。

什么是持续集成

持续集成(Continuous Integration)持续是指开发阶段,对项目进行持续性自动化编译、测试,以达到控制代码质量的手段。持续集成是一种软件开发实践。— MBALab

持续集成需要开发人员一天多次的将代码集成到主干,并进行自动化的编译,测试等操作,由于这种频繁集成,以及集成后及时开始的编译和测试,可以有效避免我们在提交代码时没有进行必要检查而导致的错误,以及一些超出预期效果的更改,从而保证代码的质量。

由于这种及时性,如果在一次提交后项目集成失败,可以快速的在这次提交中查找问题所在,缩小了找问题的范围,从而减少了一些 Debug 时间。同时如果按照这种实践,那么我们的主干代码时刻都是正确的,这样我们可以更频繁的交付。

基本要求

要将这种实践付诸实际,需要一些必要的条件,如下

  1. 一个自动构建过程,包括自动编译、分发、部署和测试等。
  2. 一个代码存储库,即需要版本控制软件来保障代码的可维护性,同时作为构建过程的素材库。
  3. 一个持续集成服务器。

自动化构建过程,可以帮助我们节省大量的时间,完成这个过程的自动化后,在以后的开发过程中,我们需要做的,就是只是提交代码到版本库中,构建自动完成,基本不再需要人工干预。

代码仓库作为构建的素材库,构建所需的代码从代码库中获得。

最好有一台服务器单独作为持续集成服务器,一方面保证了环境的纯净,一方面不影响开发,而且持续集成服务器一般是随时准备开始构建的,所以一般也不关机。

原则

  1. 开发人员必须及时向版本控制库中提交代码,也必须经常性地从版本控制库中更新代码到本地;
  2. 需要有专门的集成服务器来执行集成构建。根据项目的具体实际,集成构建可以被软件的修改来直接触发,也可以定时启动,如每半个小时构建一次;
  3. 必须保证构建的成功。如果构建失败,修复构建过程中的错误是优先级最高的工作。一旦修复,需要手动启动一次构建。
  4. 不更新构建失败的代码

开发人员及时的提交代码进行构建是符合上述实践的,及时拉取代码可以防止工作中的分支偏离主干分支太多。定时触发构建或者通过检测代码的修改情况在触发构建都是可以的,主要是根及时的构建新的代码。如果构建失败,则必要及时处理导致失败的问题,修复后重新构建。当然构建失败的代码就不要拉到本地了,会污染一个本来是可以运行的工作区。

持续集成工具

讲了这么多概念,有没有一种工具把这种实践实现呢?当然是有的,常见的持续集成工具有如下

  • Jenkins
  • Travis
  • Gitlab
  • buddybuild

仅列举了一些典型的,Jenkins 是传统型的工具,前身是 Hudson,04 年到现在已经有十多年的历史,后几个是最近几年出现的新一批,多少都和容器技术有点关系,这里我们主要介绍 Jenkins,因为这个工具比较常用,各种开发实践都可以通过大量的插件来组合实现,可定制性好很多。

Jenkins

Jenkins 是一个开源项目,提供了一种易于使用的持续集成系统,使开发者从繁杂的集成中解脱出来,专注于更为重要的业务逻辑实现上。同时 Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和提醒功能,还能用图表的形式形象地展示项目构建的趋势和稳定性。— 维基百科

Jenkins 有哪些功能呢?

  1. 定时拉取代码并编译
  2. 静态代码分析
  3. 定时打包发布测试版
  4. 自定义额外的操作,如跑单元测试等
  5. 出错提醒

基本上都是持续集成实践中的要求和周边的一些实现措施,如提醒功能等,出错后及时提醒开发者修复,Jenkins 中通过配置 SMTP 配置信息(这个一般的邮件服务提供商都有提供),邮件模板等,创建事件触发器,在事件(如编译失败)发生时,及时发送邮件通知开发者,挺方便的。

Jenkins 有很多种触发构建的方式,如 webhook,定时更新代码等,同时可以在触发构建后执行自定义的构建操作,通过编辑自定义的构建脚本,几乎可以进行任何构建操作。

上面说到单元测试,iOS 的单元测试怎么做呢?

单元测试

在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。— 维基百科

具体到 Xcode 中的单元测试,大概就是这样子的了

比如我们写了一个工具方法,需要裁剪一个图片,在中心处裁剪出来一个最大正方形,怎么测试这个方法呢?简单点来我们检查裁剪后的图片是不是宽高相等就好了,这里读取工程中的测试图片,调用方法得到新图片,一个 XCTAssertEqual 断言,如果宽高相等,则测试通过,如果不等,则失败,同样的方法我们可以写很多测试用例,在执行构建的时候进行测试。由于我对单元测试了解不深,这里就简单举例,有兴趣的话可以自己深入了解一下。

iOS 项目自动化编译

持续集成过程大部分是自动完成的,我们不可能在构建的时候还去用鼠标点几下 build 按钮,好在 Xcode 提供了命令行工具 xcodebuild,通过这个工具我们可以指定 project 或者 workspace 等参数进行编译操作,具体可以运行 man xcodebuild 查看帮助,举个例子如下

1
xcodebuild -workspace Demo.xcworkspace -scheme Demo

通常我们用 CocoaPods 管理依赖的话,我们指定的是 workspace,执行过程中我们看到如下信息

很丑对不对。。。还没有什么可读性,这里我们可以选择另外一个工具 xctool,Facebook 开源的一个工具,基于 xcodebuild 封装,用法和 xcodebuild 是一样的

1
xctool -workspace Demo.xcworkspace -scheme Demo

执行过程中看到信息如下

可读性好多了。

各个工具准备好了,那么把这些东西组合在一起,完成一个持续集成的流程。

Jenkins 安装和配置

安装

Jenkins 的安装很简单,如果你的电脑中安装有 Homebrew,只需要一条命令就可以安装

1
brew install jenkins

如果没有安装 Homebrew,那么先安装 Homebrew 吧 :) 安装方式参见官网 http://brew.sh/

启动

最新的 Homebrew 自带了服务管理工具,可以通过 brew 命令来启动或者停止服务。

如果使用 Homebrew 安装了 Jenkins,那么启动命令如下

1
brew services start jenkins

之后打开 http://127.0.0.1:8080/ 正常的话就可以看到 Jenkins 界面,初始化密码可以在~/.jenkins/secrets/initialAdminPassword 看到,填入初始密码后就可以新建自己的账户。

新建 Job

新建一个 Job,填入名字,选择 freestyle,确定即可。

配置版本库

进入建立好的 Job,进入配置选项,在 Source Code Management 中选择自己的版本管理工具,并填入地址和分支,这里以 Git 为例

设置触发器

触发器设置,这里我们设置衣每 5 分钟检查一次,有更新就开始构建,无更新则忽略。这个定时器的语法可以点输入框后的问号看到介绍。

设置构建脚本

构建脚本设置

这里我们可以通过一些插件提供的参数定制自己的操作,比如要在某个分支执行怎么样的编译等。

这里就安装和配置就完成了,返回项目页,点击 build now 开始一次构建试一下吧:)

邮件设置

在 Jenkins 的系统设置里,我们可以设置一下 SMTP 服务器的配置,这样在需要通知的时候可以通过发邮件到指定邮箱进行通知。各个邮件服务器商一般都提供有 SMTP 发信的方式,找到他们的对应配置参数在这里配置一下就可以了。

自动打包

通常我们打包的时候很麻烦,要经过 Achive-导出 IPA-上传等操作,烦琐且没什么意思,这些每次都一样的操作可以找适当的工具帮我们自动完成,Fastlane 就是这样的一个工具。Fastlane 可以自动打包并签名,还可以结合不同工具发布到不同平台,如 fir,Testflight 等 通过定义 lanes(通常在 Fastlane 文件中)定义不同的操作,最终实现一行命令实现打包并发布。

可以查看 Wikipedia 的 Fastfile 文件

https://github.com/fastlane/examples/blob/master/Wikipedia/Fastfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  desc "Runs linting (and eventually static analysis)"
  lane :analyze do
    xcodebuild(
      workspace: "Wikipedia.xcworkspace",
      scheme: "Wikipedia",
      configuration: "Debug",
      sdk: 'iphonesimulator',
      destination: 'platform=iOS Simulator,OS=9.3,name=iPhone 6',
      analyze: true
    )
  end

上面就是一个 lane,定义好 lane 之后,我们就可以通过一个命令 fastlane analyze 来执行这个 lane 中定义的操作,这些操作可以自定义,也可以通过 Fastlane 的套件中的 gym 进行打包 IPA,生成 IPA 后可以上传到 fir.im 等平台上进行测试。

通过定义好 fastlane,结合 Jenkins,我们可以实现每天定时打包,发布测试版,邮件通知测试人员等自动化操作,基本上这些东西花时间折腾一下,可以极大的简化我们的工作流,还是挺值得的。

到这里,整个持续集成相关的东西就完了,还有功能的应用需要查资料,看看怎么用,一条漫漫的折腾路啊 :)

写在后面

这篇文章基于一次分享交流的材料重新整理,若对文中讲述有不同的看法或意见,还请指出。

参考资料

– EOF –

Built with Hugo
主题 StackJimmy 设计