フードデリバリーの住所選択、店舗の配達範囲判定、ジオフェンス通知といった機能は、開発用 Mac では数回操作するだけで正常に通る一方、無人の CI パイプラインに移すと断続的に失敗しがちです。多くの場合、問題は位置計算のアルゴリズムではなく、シミュレータが前のジョブの座標や権限、実行中の経路を引き継いでいることにあります。クラウド Mac 上で位置情報テストを再現可能にするには、「位置」をシミュレータの環境情報ではなく、明示的に作成して破棄するテスト入力として扱う必要があります。
3 層の検証目的を分離する
1 本の UI テストに、座標計算、システム位置情報との連携、画面表示のアサーションをすべて担わせないでください。より安定する構成は、次の 3 層です。
- 通常のユニットテストで距離、領域、状態遷移を検証し、作成済みの座標を直接渡す。
- 少数のシミュレータテストで、システムの位置情報が実際にアプリへ渡され、想定した画面が表示されることを確認する。
- 実機で、バックグラウンド復帰、信号の揺らぎ、消費電力、センサー関連の挙動を受け入れテストする。
まず、ビジネスロジック層に対して、入力範囲を必要最小限に絞った境界を定義します。たとえば、位置情報サービスからは座標、時刻、認可状態だけを出力する設計にします。ジオフェンス判定を View Controller 内に直接分散させてはいけません。こうすれば、大半の境界条件はシミュレータを起動せずに検証でき、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"
境界テストでは、座標を 1 点だけ用意してはいけません。少なくとも領域内、境界付近、領域外の 3 組を用意し、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 の位置注入だけで実機テストを置き換えられますか?
置き換えられません。アプリ内の状態遷移や画面、業務ルールには有効ですが、バックグラウンド復帰、実際の信号変動、消費電力、センサー挙動は実機確認が必要です。
単独では成功する位置テストが CI 全体で失敗するのはなぜですか?
共有シミュレータに以前の座標、権限、実行中の GPX 経路が残ることが主因です。ジョブ専用デバイスセットを使い、前後で状態を明示的に消去します。
固定座標と GPX 経路はどう使い分けますか?
固定座標は地域や境界条件の決定的な検証に使い、GPX は連続移動と状態の順序を検証します。到着時刻を秒単位で断定するテストは避けます。
検証済みのワークフローを常時稼働するクラウドMacへ
用途に合わせてBookaMac M4またはBookaMac M4 Proを選び、注文時にリージョン、契約期間、ストレージの追加オプションをご確認ください。