在这篇文章中,我将解释我们的持续集成管道的目标以及我们用来实现这些目标的实施方法。
我们需要从CI中获得什么
速度
团队从一开始就明确表示,总的运行时间对他们来说是个大问题。快速的反馈使他们能够保持对同一任务的关注。
可靠性
现有的解决方案主要是内部机器,运行Jenkins。这有快速构建的好处,但维护它们是对开发人员的一种消耗。他们经常需要重新启动,在一个全球分布的团队中,这对生产力来说并不是好事。同时,也很难维护干净的环境和统一的设置。
灵活性
当我们在调查其他解决方案时,使用的灵活性成为一个问题。我们的每个项目都有许多依赖性,例如跨平台的Appium测试或需要Homebrew的工具。
标准化
这与灵活性是一致的,但在每个项目中拥有一个CI标准是有益的,因为知识可以在团队和开发人员之间共享。CircleCI已经被其他团队和项目独立采用,这让我们在选择它作为我们需要的解决方案时感到更有信心。
通过使用CircleCI,这些要求大部分都得到了满足。Circle的正常运行时间大于99%,而且由于其他团队已经在使用它,所以标准化的速度也比较快。我们的管道中唯一需要努力的方面是执行速度。当我们第一次对CircleCI配置文件进行最基本的迭代时,我们的运行时间超过一个小时(1.5小时)。这比我们的自我托管机器的运行时间(30分钟)少了一半,这是个巨大的增长。
我们为改善运行时间所做的前三项改变
平行性
平行性是改进任何CI工作流程的一个重要部分。将测试分割成并发的流程意味着运行时间被减少到最长运行作业的长度。此外,使用CircleCI作业进行拆分,使我们有能力重新运行一个作业,大大减少重新运行的时间。
测试iOS应用程序比测试Web应用程序更复杂,因为我们需要传递构建的二进制,以便测试有东西可以运行。为了实现这一点,我们在config.yml.NET中创建了两个作业。
构建
- 检验代码
- 恢复缓存
- 捆绑安装
- 安装Cocoapods
- 从Brewfile安装
- 安装基于SPM的工具
- 解决Swift软件包的依赖性问题
- 构建衍生数据
- 保存新的缓存
- 坚持到工作区
运行
- 附加到工作区
- 捆绑安装
- 运行Fastlane lane
- 准备存储的测试结果
- 存储测试结果
- 存储工件
CircleCI中iOS并行的关键是persist_to_workspace ,它将保存你定义的路径,以便它们可以被另一个作业访问。由于每个作业都是一个干净的实例,这就是我们在它们之间维护状态的方式。
坚持整个项目,包括从构建中得到的数据,对于我们的主要项目来说,几乎是8Gb。这本身就需要10分钟的时间来再次上传和下载。通过只保存所需的文件和文件夹,我们看到了更好的性能。
- persist_to_workspace:
root: /Users/distiller # Default path for a Circle job
paths:
- project/DerivedData/Build/Products # This is the built app that can then be ran against for testing
- project/fastlane # So we can run Fastlane on the parallel jobs
- project/Gemfile # So we can run Fastlane on the parallel jobs
- project/Gemfile.lock # So we can run Fastlane on the parallel jobs
- project/SnapshotTests # Contains the source snapshot images
- 'project/UnitTests/ViewControllerTests/__Snapshots__' # Contains the source snapshot images
- project/commit_author # Used for the Slack message integration in Fastlane
- .gem # So we can run Fastlane on the parallel jobs
我们维护一个共享的Fastlane通道库,在每个项目中使用。在CI服务和我们的底层业务逻辑之间有一个调解器,使未来的变化更容易。xcodebuild和Fastlane都有能力为测试进行构建,并在不构建的情况下再次运行它们。
运行作业可以简单地检索所有这些存储的状态与。
- attach_workspace: # Gives this job access to the results stored by the build job, so that many parallel jobs can be used from the result
at: /Users/distiller
我们把测试分成了测试计划。我们有快照、单元和UI测试。到目前为止,UI测试是最慢的,我们有多个作业来分割它们。
我们的目标是让每个测试有大致相同的执行时间。将它们收集在一个类似的用例下,并提供一个描述性的名字,可以帮助开发人员快速了解什么是失败的。
回归测试
在合并前测试拉动请求(PR)的目的是确保现有的代码不会受到某种递减方式的影响。这不需要运行每一个测试;一般来说,只要定期运行全套测试,就可以用一个子集来验证这些变化。
通过使用更多的测试计划,我们把回归和PR测试分开。回归测试在PR上有一个可选的保持步骤,所以如果需要的话可以运行它们。然后根据每个团队的时区在整个工作周内安排回归。
scheduled_regression:
triggers:
- schedule:
cron: "0 05,18,23 * * 1-5" # 18:00 NZDT, 18:00 GMT, 18:00 EST, Monday - Friday (Circle uses UTC)
filters:
branches:
only:
- develop
调整回归和PR测试的内容可以让你的PR运行时间更快,同时仍然保持良好的覆盖率。开发人员也有控制权,如果他们觉得他们的功能需要,可以轻松地运行所有的测试。

缓存
在这个项目中,每次构建都有8GB的数据需要通过Swift Package Manager(SPM)下载。这给构建工作增加了大量时间,所以我们利用缓存来加快构建速度。
在构建步骤中,我们恢复现有的缓存,解析软件包,并为下一个工作保存缓存。解析是一个必要的步骤,因为它告诉Xcode根据新的本地包来刷新SPM。
复原
- restore_cache: # To speed up builds we cache the SPM packages and use the resolved file as a hash
key: spm-cache-v4-{{ checksum "Project.xcworkspace/xcshareddata/swiftpm/Package.resolved" }}
解析
lane :resolve_cached_spm_dependencies do |options|
if options[:xcode_scheme].nil? || options[:derived_data_path].nil?
UI.user_error!("Resolving dependencies was invoked, but an `xcode_scheme` or `derived_data_path` were not provided.")
end
if options[:workspace_name].nil? == false
# Resolve dependencies and point to cache directory
sh("xcodebuild -resolvePackageDependencies -workspace ../#{options[:workspace_name]}.xcworkspace -scheme #{options[:xcode_scheme]} -clonedSourcePackagesDirPath #{options[:derived_data_path]}/SourcePackages")
elsif options[:project_name].nil? == false
sh("xcodebuild -resolvePackageDependencies -project ../#{options[:project_name]}.xcodeproj -scheme #{options[:xcode_scheme]} -clonedSourcePackagesDirPath #{options[:derived_data_path]}/SourcePackages")
else
UI.user_error!("Resolving dependencies was invoked, but a `workspace_name` or `project_name` were not provided.")
end
end
保存
- save_cache:
paths:
- ./DerivedData/SourcePackages
key: spm-cache-v4-{{ checksum "Project.xcworkspace/xcshareddata/swiftpm/Package.resolved" }}
我们注意到使用缓存后,构建时间有了明显的改善。只有当解析文件的内容发生变化时才需要更新,例如当一个包被更新或添加时。
总结
通过对我们的CircleCI配置的这些改变,我们看到我们的测试运行时间有了明显的减少。总体而言,PR测试的运行时间从1.5小时减少到30分钟。
我们的管道仍然可以更快。但是,当我们把以下的好处结合起来。
- 减少浮动性
- 零硬件维护和停机时间
- 对安全没有要求,如VPN等。
- 没有现场重启硬件的要求
我们发现这是一个可以接受的未来解决方案,并将继续为未来更快、更智能的CI而努力。