BookaMac 工程记录

云端 Mac 用 simctl 建立可重复的 iOS 定位回归测试

云端 Mac 用 simctl 建立可重复的 iOS 定位回归测试

外卖地址选择、门店范围判断、地理围栏提醒这类功能,往往在开发机上点几下就能通过,进入无人值守流水线后却开始偶发失败。问题通常不在定位算法,而在模拟器继承了上一轮任务的坐标、权限或仍在运行的路线。要让云端 Mac 上的定位测试可重复,必须把“位置”当成需要显式创建和销毁的测试输入,而不是模拟器的环境背景。

先拆开三层验证目标

不要让一条 UI 测试同时承担坐标计算、系统定位接入和界面断言。更稳妥的结构分为三层:

  1. 用普通单元测试验证距离、区域和状态转换,直接传入构造好的坐标。
  2. 用少量模拟器测试确认系统定位数据确实进入应用,并触发预期页面。
  3. 用真机验收后台唤醒、信号漂移、耗电和传感器相关行为。

可以先为业务层定义一个很窄的输入边界,例如让位置服务只输出坐标、时间与授权状态。围栏判断不要直接散落在视图控制器里。这样,绝大多数边界条件无需启动模拟器,只有系统接线部分才依赖 simctl location

模拟坐标能证明应用如何处理某个位置,不能证明真实无线环境会以相同节奏交付位置事件。

为每个任务准备独立模拟器

先固定 Xcode 路径,再列出当前机器实际安装的设备类型与运行时。不要把示例中的运行时标识直接永久写死,因为升级 Xcode 后标识可能变化。

sudo xcode-select -s /Applications/Xcode.app
xcrun simctl list devicetypes
xcrun simctl list runtimes

流水线应创建独立的 CoreSimulator 设备集,并把生成的 UDID 保存到当前任务。下面的设备类型和运行时标识需要替换成上一条命令真实返回的值。

set -euo pipefail

DEVICE_SET="$PWD/.simulator-location-tests"
DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-16"
RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-18-0"

rm -rf "$DEVICE_SET"
UDID="$(xcrun simctl --set "$DEVICE_SET" create GeoTest "$DEVICE_TYPE" "$RUNTIME")"
xcrun simctl --set "$DEVICE_SET" boot "$UDID"
xcrun simctl --set "$DEVICE_SET" bootstatus "$UDID" -b

独立设备集比反复擦除公共模拟器更容易排查:失败任务的设备目录可以暂时保留,成功任务则直接删除。并行任务也不会争抢同一台已启动设备。

注入固定坐标与权限

固定坐标适合验证“位于区域内”“越过边界”“远离目标点”这类确定性条件。应先安装应用、预置权限、设置坐标,再启动测试目标。若应用启动后才改权限,首屏可能已经记录了未授权状态。

BUNDLE_ID="com.example.LocationDemo"

xcrun simctl --set "$DEVICE_SET" privacy "$UDID" grant location "$BUNDLE_ID"
xcrun simctl --set "$DEVICE_SET" location "$UDID" set 1.290270,103.851959

xcodebuild test \
  -workspace LocationDemo.xcworkspace \
  -scheme LocationDemo \
  -destination "platform=iOS Simulator,id=$UDID" \
  -resultBundlePath "$PWD/TestResults/LocationTests.xcresult"

边界测试不要只放一个点。至少准备区域内、边界附近和区域外三组坐标,并让测试数据说明预期结果,而不是用 case1case2 之类名称隐藏含义。

场景 输入设计 建议断言
区域内 距中心明显小于阈值 状态稳定为区域内
边界附近 阈值两侧各放一点 比较规则与取整一致
区域外 距中心明显大于阈值 不触发区域内动作
权限关闭 撤销定位授权 展示可恢复的引导状态

如果应用缓存了最后一次位置,还要在每个用例前清除应用数据或通过测试启动参数禁用持久缓存,否则新坐标可能被旧值覆盖。

用 GPX 验证移动顺序

连续移动应使用短小、可读的 GPX 文件。路线只保留能触发关键状态变化的途经点,不要复制一整段真实轨迹。以下文件从区域外移动到区域内:

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="location-ci">
  <wpt lat="1.320000" lon="103.820000">
    <name>outside</name>
  </wpt>
  <wpt lat="1.290270" lon="103.851959">
    <name>inside</name>
  </wpt>
</gpx>

执行路线时,断言应围绕事件顺序,而不是依赖精确到秒的等待时间。

xcrun simctl --set "$DEVICE_SET" location "$UDID" start "$PWD/Fixtures/enter-region.gpx"
xcodebuild test \
  -workspace LocationDemo.xcworkspace \
  -scheme LocationRouteTests \
  -destination "platform=iOS Simulator,id=$UDID"
xcrun simctl --set "$DEVICE_SET" location "$UDID" stop

应用侧可以记录脱敏后的状态序列,例如 outside → approaching → inside,并为每次变化附上单调递增时间。不要把完整坐标写进长期日志;失败证据只需保留用例名、预期区域、实际状态和有限的小数位。

清理状态并保留失败证据

清理不能只在成功分支执行。脚本开始后立即注册退出处理,停止路线、清除定位并关闭设备;是否删除设备集则根据测试结果决定。

cleanup() {
  xcrun simctl --set "$DEVICE_SET" location "$UDID" stop 2>/dev/null || true
  xcrun simctl --set "$DEVICE_SET" location "$UDID" clear 2>/dev/null || true
  xcrun simctl --set "$DEVICE_SET" shutdown "$UDID" 2>/dev/null || true
}
trap cleanup EXIT

失败时应归档 xcresult、应用日志、所用 GPX 文件、设备与运行时标识,以及测试开始前的权限状态。截图只能辅助判断,不能替代结构化断言。若同一坐标重复失败,先检查设备集是否真正隔离、Bundle ID 是否正确、权限是否在启动前授予,再检查业务计算。

一套稳定的定位流水线最终应做到:测试输入可读、模拟器状态一次性使用、路线必定停止、业务逻辑可脱离系统定位单测。这样出现失败时,团队看到的是明确的状态差异,而不是一句无法复现的“定位偶尔不准”。

常见问题

simctl 注入定位后,能否替代真实设备测试?

不能。它适合验证定位数据进入应用后的状态转换、界面与业务规则;后台唤醒、真实信号漂移、功耗和复杂传感器行为仍需在真实设备上验收。

为什么定位测试单独运行能过,进入完整流水线后却失败?

最常见原因是复用了模拟器、权限或上一次注入的坐标。为每个任务使用独立设备集,并在前后显式设置权限、停止路线和清除定位状态。

固定坐标与 GPX 路线应该如何分工?

固定坐标用于城市、区域和边界条件的确定性断言;GPX 路线用于验证连续移动、途经点顺序和状态变化,不应依赖精确到秒的到达时间。

独享物理 Mac mini

把已验证的工作流放到持续在线的云端 Mac

按用途选择 BookaMac M4 或 BookaMac M4 Pro,并在下单时核对地区、租期与存储附加项。

选择租用方案