外卖地址选择、门店范围判断、地理围栏提醒这类功能,往往在开发机上点几下就能通过,进入无人值守流水线后却开始偶发失败。问题通常不在定位算法,而在模拟器继承了上一轮任务的坐标、权限或仍在运行的路线。要让云端 Mac 上的定位测试可重复,必须把“位置”当成需要显式创建和销毁的测试输入,而不是模拟器的环境背景。
先拆开三层验证目标
不要让一条 UI 测试同时承担坐标计算、系统定位接入和界面断言。更稳妥的结构分为三层:
- 用普通单元测试验证距离、区域和状态转换,直接传入构造好的坐标。
- 用少量模拟器测试确认系统定位数据确实进入应用,并触发预期页面。
- 用真机验收后台唤醒、信号漂移、耗电和传感器相关行为。
可以先为业务层定义一个很窄的输入边界,例如让位置服务只输出坐标、时间与授权状态。围栏判断不要直接散落在视图控制器里。这样,绝大多数边界条件无需启动模拟器,只有系统接线部分才依赖 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"
边界测试不要只放一个点。至少准备区域内、边界附近和区域外三组坐标,并让测试数据说明预期结果,而不是用 case1、case2 之类名称隐藏含义。
| 场景 | 输入设计 | 建议断言 |
|---|---|---|
| 区域内 | 距中心明显小于阈值 | 状态稳定为区域内 |
| 边界附近 | 阈值两侧各放一点 | 比较规则与取整一致 |
| 区域外 | 距中心明显大于阈值 | 不触发区域内动作 |
| 权限关闭 | 撤销定位授权 | 展示可恢复的引导状态 |
如果应用缓存了最后一次位置,还要在每个用例前清除应用数据或通过测试启动参数禁用持久缓存,否则新坐标可能被旧值覆盖。
用 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
按用途选择 BookaMac M4 或 BookaMac M4 Pro,并在下单时核对地区、租期与存储附加项。