- 移动开发
- UI组件
【免费下载链接】Instructions
Create walkthroughs and guided tours (coach marks) in a simple way, with Swift.
本文以 fastlane/README.md 与 fastlane/Fastfile 为主体,系统讲解 iOS 开源库 Instructions(Swift 编写的 coach marks / 引导页框架)如何使用 fastlane 实现「构建框架与示例工程、运行单元与 UI 测试、生成并校验快照」的自动化流水线。读完本文,你将掌握每个 lane 的调用方式、参数含义与底层实现,能够基于这份配置搭建属于自己的 iOS 持续集成流程,并理解快照测试从「录制参考图」到「回归校验」的完整工作机制。
一、fastlane 在 Instructions 项目中的角色
Instructions 是一个同时以 SwiftPM(见 Package.swift)、CocoaPods(见 Instructions.podspec)等形式分发的 iOS 框架仓库,其工程以 workspace 形式组织(见 Instructions.xcworkspace/contents.xcworkspacedata),包含四个可构建的 target scheme:
| Scheme | 用途 |
|---|---|
Instructions | 框架本体 |
Instructions App Extensions | 面向 App Extensions 的框架变体 |
Instructions Example | 演示示例 App(含单元测试、UI 测试与快照测试) |
Instructions App Extensions Example | 演示键盘扩展的示例工程 |
fastlane 的作用就是把这四类 scheme 的「构建」与「测试」全部收敛为一条条可复用的 lane,让本地开发与 CI 都能用同一套命令完成校验。fastlane/README.md 是 fastlane 在每次运行后自动重新生成的文档,它忠实列出了当前 Fastfile 中暴露的所有 lane 与对应命令——它是 Fastfile 的「自动索引」,本文将以这份清单为骨架,结合 fastlane/Fastfile 的源码逐层拆解。
二、环境准备与依赖安装
在运行任何 lane 之前,需要先完成两件事:
- 安装最新版本的 Xcode Command Line Tools:
xcode-select --install- 通过 Bundler 安装 fastlane 及配套依赖。仓库的 Gemfile 声明了运行全部流水线所需的两个 gem:
# frozen_string_literal: true source "https://rubygems.org" gem "fastlane" gem 'slather'fastlane:负责全部构建、测试 lane;slather:用于生成代码覆盖率报告(CI 中通过slather coverage -x --build-directory Build/生成 cobertura 格式覆盖率并上报)。
Gemfile.lock 锁定了当时依赖树(fastlane 2.216.0、slather 2.7.5,支持 arm64-darwin 与 x86_64-linux 平台)。安装命令:
bundle install之后所有 lane 统一通过 Bundler 前缀调用,保证 CI 与本地使用完全一致的 fastlane 版本:
bundle exec fastlane ios <lane_name>说明:仓库的 fastlane/Appfile 目前仅保留了注释模板(
app_identifier与apple_id均未启用)。原因在于本仓库的 lane 只执行构建与测试,不涉及上传 App Store / TestFlight 等需要 Apple 账号鉴权的操作,因此不需要在 Appfile 中配置 bundle identifier 与 Apple ID。若你要在自己的项目中复用这套 lane,再按需取消注释即可。
三、lane 全景一览
fastlane/README.md 列出的全部 lane 及其命令、用途如下:
| Lane 名称 | 命令 | 用途 |
|---|---|---|
ios build_all | [bundle exec] fastlane ios build_all | 构建全部 target |
ios build_frameworks | [bundle exec] fastlane ios build_frameworks | 构建框架(Instructions 及其 App Extensions 变体) |
ios build_examples | [bundle exec] fastlane ios build_examples | 构建示例工程 |
ios build | [bundle exec] fastlane ios build | 构建指定的 scheme |
ios generate_snapshots | [bundle exec] fastlane ios generate_snapshots | 为所有模拟器生成新的快照参考图 |
ios test_all | [bundle exec] fastlane ios test_all | 运行完整测试套件 |
ios test_snapshots | [bundle exec] fastlane ios test_snapshots | 运行快照回归测试 |
ios test_unit_and_ui | [bundle exec] fastlane ios test_unit_and_ui | 运行单元测试与 UI 测试 |
其中build_all与test_all是复合 lane,分别聚合了「两个构建子流程」与「两个测试子流程」,构成了仓库的核心 CI 路径。
四、Fastfile 全局配置解读
进入各 lane 之前,先看 fastlane/Fastfile 中定义的全局常量,它们贯穿所有 lane:
default_platform(:ios) platform :ios do DEVICES = [ "iPad (10th generation)", "iPhone 15 Pro", "iPhone SE (3rd generation)" ] WORKSPACE = "Instructions.xcworkspace" INSTRUCTIONS_SCHEME = "Instructions" APP_EXTENSION_INSTRUCTIONS_SCHEME = "Instructions App Extensions" EXAMPLE_SCHEME = "Instructions Example" APP_EXTENSION_EXAMPLE_SCHEME = "Instructions App Extensions Example" DERIVED_DATA_PATH = "Build/"DEVICES:测试覆盖的三款模拟器设备——iPad(第 10 代)、iPhone 15 Pro、iPhone SE(第 3 代),同时覆盖了 iPad 与不同屏幕形态的 iPhone,确保快照测试在横竖屏、异形屏(刘海)等场景下都有参考;WORKSPACE:统一使用Instructions.xcworkspace而非单一 xcodeproj,以便同时编译框架与两个示例工程;- 四个 scheme 常量:区分「框架本体 / 框架扩展变体 / 示例 App / 扩展示例 App」四类 target;
DERIVED_DATA_PATH:把 DerivedData 固定到仓库根目录的Build/,这一约定同时被 CI 中的 slather 覆盖率收集步骤复用(--build-directory Build/)。
五、构建 lane:从复合到单点
5.1 build_all:一键构建全部 target
desc "Build all targets" lane :build_all do build_frameworks build_examples endbuild_all依次调用build_frameworks与build_examples,是「全量构建」的入口,等价于把框架与示例工程全部编译一遍,用于验证整个仓库可构建。
5.2 build_frameworks 与 build_examples
desc "Build frameworks" lane :build_frameworks do build(scheme: INSTRUCTIONS_SCHEME) build(scheme: APP_EXTENSION_INSTRUCTIONS_SCHEME) end desc "Build examples" lane :build_examples do build(scheme: EXAMPLE_SCHEME) build(scheme: APP_EXTENSION_EXAMPLE_SCHEME) end两个 lane 都只是以不同的 scheme 参数调用底层的buildlane:
build_frameworks验证框架本体(Instructions)与 App Extensions 变体(Instructions App Extensions)能否编译通过;build_examples验证两个示例工程(Instructions Example、Instructions App Extensions Example)能否编译通过。
5.3 build:单一 scheme 的构建实现
desc "Build the given scheme" lane :build do |values| build_app( workspace: WORKSPACE, scheme: values[:scheme], clean: true, skip_archive: true, skip_codesigning: true ) endbuild接收values[:scheme]作为参数,通过 fastlane 内建的build_appaction 完成编译。三个关键参数的含义:
clean: true:每次构建都执行 clean,确保产物不受增量编译缓存污染;skip_archive: true:仅编译、不归档,因为这里只需要验证「能构建」,不需要产出可分发的归档包;skip_codesigning: true:跳过代码签名。库的构建验证不涉及真机安装或发布,跳过签名即可在无开发者证书的 CI 环境直接运行。
六、测试 lane:三层测试体系
6.1 test_all:完整测试套件入口
desc "Run full test suite" lane :test_all do test_snapshots() test_unit_and_ui() endtest_all串联两条测试流水线:先跑快照测试,再跑单元与 UI 测试。这也是 .github/workflows/continuous-integration.yml 中 CI 主流程调用的 lane(bundle exec fastlane test_all)。
6.2 test_unit_and_ui:单元测试 + UI 测试
desc "Run Unit & UI tests" lane :test_unit_and_ui do run_tests( workspace: WORKSPACE, scheme: EXAMPLE_SCHEME, testplan: "Unit+UI", devices: DEVICES[0], prelaunch_simulator: true, derived_data_path: DERIVED_DATA_PATH, ) end该 lane 使用run_tests在Instructions Examplescheme 下执行名为Unit+UI的测试计划(testplan)。Examples/Unit+UI.xctestplan 定义了这个计划的测试目标:
InstructionsExampleUITests(UI 测试,parallelizable)InstructionsUnitTests(单元测试,parallelizable)
两者均标记为parallelizable,可在模拟器上并行执行以缩短总耗时。该 lane 只在DEVICES[0](iPad 第 10 代)上运行;prelaunch_simulator: true用于在测试开始前预热模拟器,减少首轮启动带来的超时抖动。
6.3 test_snapshots:快照回归测试
desc "Run snapshot tests" lane :test_snapshots do run_tests( workspace: WORKSPACE, scheme: EXAMPLE_SCHEME, testplan: "Snapshot", only_test_configurations: "Test Snapshots", devices: DEVICES, prelaunch_simulator: true, derived_data_path: DERIVED_DATA_PATH, ) end快照测试在全部三台模拟器(DEVICES)上运行,使用Snapshot测试计划中的Test Snapshots配置。Examples/Snapshot.xctestplan 中该配置的关键是两条环境变量(同时出现在defaultOptions中,作用于整个计划):
FB_REFERENCE_IMAGE_DIR→$(SRCROOT)/Example/Snapshot Tests/Supporting Files/Snapshots/ReferenceImages:参考图目录,即$(SRCROOT)/Example/Snapshot Tests/Supporting Files/Snapshots/ReferenceImages_64(仓库中以_64后缀落盘);IMAGE_DIFF_DIR→$(SRCROOT)/Example/Snapshot Tests/Supporting Files/Snapshots/FailureDiffs:测试失败时的差异图目录。
该计划的测试目标是InstructionsSnapshotTests(同样 parallelizable),入口文件为 Examples/Example/Snapshot Tests/DefaultExampleSnapshotTests.swift。它的setUp中有两行与 fastlane 流水线直接呼应的逻辑:
fileNameOptions = [.screenScale] recordMode = ProcessInfo.processInfo.environment["RECORD_MODE"] != nil也就是说:测试进程是否处于「录制模式」,完全由环境变量RECORD_MODE是否存在决定——这正是下面generate_snapshotslane 切换测试行为的开关。
6.4 generate_snapshots:生成/更新参考图
desc "Generate new snapshots for all simulators" lane :generate_snapshots do run_tests( workspace: WORKSPACE, scheme: EXAMPLE_SCHEME, testplan: "Snapshot", only_test_configurations: "Generate Snapshots", devices: DEVICES ) end与test_snapshots的差异只有一处:only_test_configurations从Test Snapshots变为Generate Snapshots。后者的配置(见 Examples/Snapshot.xctestplan)额外注入了:
"environmentVariableEntries" : [ { "key" : "RECORD_MODE", "value" : "true" }, ... ]因此当快照测试用例检测到RECORD_MODE=true时,DefaultExampleSnapshotTests.swift 会把recordMode置为真,测试不再比对参考图,而是把当前画面写盘作为新的参考图。这就是 Instructions 快照工作流的核心闭环:
改动 UI / 布局或新增测试用例 │ ▼ fastlane ios generate_snapshots (RECORD_MODE=true,录制新参考图) │ ▼ 提交新生成的 ReferenceImages │ ▼ fastlane ios test_snapshots (无 RECORD_MODE,回归比对)快照用例本身覆盖了两种演示维度(见 DefaultExampleSnapshotTests.swift):
- 三种展示上下文:
independentWindow(独立窗口)、sameWindow(同窗口)、controller(控制器内); - 两种行为模式:
testFlowIn*(教练标记依次推进的完整流程)与testRotationIn*(流程中途旋转屏幕)。
每个用例对同一场景连续截取 5 张快照(for _ in 0..<5),比对时使用perPixelTolerance: 0.1与overallTolerance: 0.002两档容差(见 DefaultExampleSnapshotTests.swift)。此外,为保证快照稳定,测试会通过--SnapshotTests启动参数隐藏状态栏(见 Examples/Example/Snapshot Tests/Extensions/XCUIApplication+Launch.swift),并借助 DeviceKit 扩展按设备型号精确裁剪状态栏 / Home Indicator 区域(见 Examples/Example/Snapshot Tests/Extensions/UIImage+Extensions.swift),确保参考图不受系统 UI 干扰、可跨设备复现。
七、与 CI 的集成:一条命令跑完整套件
仓库的 .github/workflows/continuous-integration.yml 展示了这条流水线在实际 CI 中的用法。其testjob 在macos-13(Xcode 14.3.1)上执行:
bundle install bundle exec fastlane test_all即一次调用聚合了test_snapshots+test_unit_and_ui。CI 中还有两个与 fastlane 配置强相关的配套环节:
- 失败差异图上传:若
test_all失败,把Examples/Example/Snapshot Tests/Supporting Files/Snapshots/FailureDiffs/作为 artifact 上传,方便人工定位快照偏差; - 覆盖率收集:通过
slather coverage -x --build-directory Build/生成 cobertura 格式覆盖率,再由 CodeClimate 上报。其配置见 .slather.yml——source_directory: Sources、output_directory: Tests,并忽略Examples/*(示例代码不计入框架覆盖率)。
此外 CI 还通过独立的 InstructionsPackageTest 仓库分别验证 SwiftPM、CocoaPods、Carthage 三种安装方式下的导入与构建,与本仓库 Fastfile 的构建 lane 形成互补:Fastfile 管「仓库内部全量构建 + 测试」,CI 额外管「第三方集成方式可用性」。
八、常见问题与实操建议
为什么所有命令都要加
bundle exec?fastlane/README.md 中每个 lane 的调用方式都写作[bundle exec] fastlane ...,方括号表示可选。推荐始终使用bundle exec,以锁定 Gemfile.lock 中记录的 fastlane 2.216.0 版本,避免本机全局 fastlane 版本差异导致行为不一致。改完 UI 后快照测试红了怎么办?这是快照测试的正常预期:UI 变化导致画面与参考图不一致。正确的处理顺序是:先人工核对
FailureDiffs目录下的差异图,确认改动符合预期,再运行bundle exec fastlane ios generate_snapshots重新录制参考图,最后提交新参考图并跑一遍ios test_snapshots验证。只想快速验证一个 scheme 能否编译?运行
bundle exec fastlane ios build scheme:YourScheme。注意build是唯一接收参数的 lane,其余 lane 均已固化 scheme。新增测试用例后如何纳入流水线?单元测试加入
InstructionsUnitTests、UI 测试加入InstructionsExampleUITests即可被 Examples/Unit+UI.xctestplan 自动拾取;新增快照用例则需在InstructionsSnapshotTests中编写并先跑generate_snapshots生成参考图。
九、总结
本文以 fastlane 自动生成的 lane 清单为索引,逐条对照 fastlane/Fastfile 的实现,完整还原了 Instructions 仓库的自动化流水线:四条构建 lane(build_all/build_frameworks/build_examples/build)负责四个 scheme 的编译验证;四条测试 lane(test_all/test_snapshots/test_unit_and_ui/generate_snapshots)分别承载单元测试、UI 测试与快照测试,其中RECORD_MODE环境变量配合 Examples/Snapshot.xctestplan 的两种配置,实现了「录制参考图 ↔ 回归比对」的可逆闭环。这套配置同时服务于本地开发与 GitHub Actions 持续集成,可以作为任何 iOS 库项目搭建「构建 + 测试 + 快照 + 覆盖率」一体化流水线的直接范本。
- 移动开发
- UI组件
【免费下载链接】Instructions
Create walkthroughs and guided tours (coach marks) in a simple way, with Swift.
相关推荐
终极BottomNavigation测试指南:从单元测试到UI自动化的完整实践方案
终极BottomNavigation测试指南:从单元测试到UI自动化的完整实践方案 BottomNavigation是一个帮助开发者轻松实现Google底部导航
语言运行时后端开发工具包管理器前端构建测试WPF测试策略:单元测试与UI自动化测试全方案
WPF测试策略:单元测试与UI自动化测试全方案 你还在为WPF应用的测试覆盖率发愁吗?是否在单元测试与UI自动化测试之间难以平衡?本文将系统讲解WPF应用的测试
桌面应用前端UI组件LCD Image Converter:重新定义嵌入式图像与字体转换的完整解决方案
LCD Image Converter:重新定义嵌入式图像与字体转换的完整解决方案 LCD Image Converter是一款革命性的专业图像转换工具,专为嵌
桌面应用嵌入式开发工具图像处理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考