simctl addmediaで再現可能なiOS写真取り込みテストを作る
ユーザーが写真ライブラリから画像を選択すると、アプリでは向き情報の読み取り、サムネイルの生成、元画像の圧縮、アップロードが必要になることがあります。動画ではさらに、再生時間、エンコード、ファイルサイズによる分岐が加わります。開発用Macで一度手動操作しただけでは、次のビルドでも同じ入力セットを処理できるとは限りません。より確実な方法は、メディアファイルをテストフィクスチャとしてリポジトリに含め、クラウドMac上で専用シミュレータをリセットしてから、simctl addmediaで写真ライブラリへ投入することです。
まず検証対象の境界を定義する
この種のテストでは、システムの写真アプリを自動操作するのではなく、アプリとシステム写真ライブラリの境界に焦点を当てます。少なくとも、次の4つの経路をカバーすることを推奨します。
- アプリが想定した数の画像と動画を検出できる。
- ピッカーから結果が返された後、ファイル形式、ピクセル寸法、動画の再生時間が想定どおりである。
- 圧縮またはアップロード処理で、偶然取得したプレビューではなく元のリソースが使われる。
- 未対応形式、巨大なファイル、権限の変更が明確なエラー分岐に入る。
simctl addmediaは、シミュレータ上の読み込みと処理フローを検証するのに適しています。ただし、カメラ撮影、デバイス固有のエンコード、実際のストレージ負荷のテストを代替するものではありません。こうした境界をテストの説明に明記しておけば、失敗時にシミュレータの制約を製品の不具合と誤認せずに済みます。
回帰テストの目的は、できるだけ多くの素材を投入することではありません。各素材を、名前を付けられ、反復でき、診断可能な1つの動作に対応させることです。
小さく安定したメディアフィクスチャを用意する
リポジトリにTests/Fixtures/Mediaを作成し、テストに必要な最小限のファイルだけをコミットします。ファイル名から想定用途が分かるようにし、カメラが自動生成した名前は使わないようにします。
| ファイル | 用途 | 主なアサーション |
|---|---|---|
portrait.jpg |
向き情報を含む縦長画像 | 向き補正後の幅と高さが正しい |
transparent.png |
透過チャンネルを含む画像 | エクスポート後も透明度が失われていない |
short.mov |
短い動画 | 再生時間とメディアタイプが正しい |
invalid.txt |
メディアではないファイル | 投入処理で明示的に拒否される |
画像ツールによる意図しないフィクスチャの書き換えを防ぐため、SHA-256の一覧も保存します。
cd Tests/Fixtures/Media
shasum -a 256 portrait.jpg transparent.png short.mov > SHA256SUMS
shasum -a 256 -c SHA256SUMS
ジョブの実行中にフィクスチャを再エンコードしないでください。システムのバージョンによっては、エンコーダが異なるメタデータを書き込む可能性があります。その結果、問題が「アプリの挙動が変化した」ことではなく、「入力自体がすでに変化していた」ことになってしまいます。動的なファイルが本当に必要な場合は、生成スクリプト、パラメータ、生成後のダイジェストをまとめて記録します。
ジョブごとにシミュレータを分離する
長時間稼働するシミュレータを共有すると、写真ライブラリのアセット、権限の記録、バックグラウンドプロセスが残ります。最も単純な基準は、スケジューリング層からDEVICE_UDIDを渡し、ジョブ開始後にシミュレータを終了して消去し、起動完了まで待機することです。
set -euo pipefail
: "${DEVICE_UDID:?DEVICE_UDID is required}"
: "${BUNDLE_ID:?BUNDLE_ID is required}"
xcrun simctl shutdown "$DEVICE_UDID" 2>/dev/null || true
xcrun simctl erase "$DEVICE_UDID"
xcrun simctl boot "$DEVICE_UDID"
xcrun simctl bootstatus "$DEVICE_UDID" -b
xcrun simctl privacy "$DEVICE_UDID" reset photos "$BUNDLE_ID" || true
xcrun simctl privacy "$DEVICE_UDID" grant photos "$BUNDLE_ID"
シミュレータの機種とシステムバージョンもジョブ設定で固定する必要があります。name=iPhoneだけを指定して、Xcodeに選択を任せてはいけません。並列ジョブでは異なるUDIDを使用します。そうしなければ、一方のジョブがeraseを実行している最中に、もう一方のジョブがテストを実行している可能性があります。
権限テストは2つのグループに分けます。メインフローでは写真ライブラリへのアクセス権を事前に付与してメディア処理を検証します。権限拒否フローではrevokeを別途実行し、アプリの案内と復旧手段だけをアサートします。システムダイアログが表示されるかどうかで、テストの成否が決まらないようにしてください。
素材を投入して対象テストを実行する
投入前に拡張子とダイジェストを確認し、対応しているファイルをまとめてaddmediaへ渡します。
FIXTURES="$PWD/Tests/Fixtures/Media"
shasum -a 256 -c "$FIXTURES/SHA256SUMS"
xcrun simctl addmedia "$DEVICE_UDID" \
"$FIXTURES/portrait.jpg" \
"$FIXTURES/transparent.png" \
"$FIXTURES/short.mov"
xcodebuild test \
-workspace Demo.xcworkspace \
-scheme Demo \
-destination "platform=iOS Simulator,id=$DEVICE_UDID" \
-only-testing:MediaImportTests \
-resultBundlePath "$PWD/Artifacts/MediaImport.xcresult"
addmediaが成功を返しても、それはコマンドがファイルを受け付けたことを示すだけです。アプリから正しいアセットがすでに見えているとは限りません。テストコードでは、写真ライブラリの検索結果が想定件数に達するまで待機し、有限のタイムアウトを設定します。アサーションには、メディアタイプ、ピクセル寸法、再生時間の範囲、ビジネスロジックの生成結果を優先して使います。取り込み時刻とリソースの作成時刻は異なる場合があるため、システム写真ライブラリ上の表示順序には依存しないでください。
アプリがファイルをアップロードする場合は、ネットワーク層をローカルのテスト実装に置き換え、受信したMIMEタイプ、バイト数、ダイジェストを記録できます。これにより、元のリソースが読み込まれていることを確認しつつ、外部ネットワークの変動が回帰テストの結果に混入するのを防げます。
失敗結果を後から確認できるようにする
失敗したジョブでは、少なくとも3種類の証拠を保存します。xcresult、フィクスチャのダイジェスト、その実行で使用したデバイス情報です。現在利用可能なシミュレータの一覧を追加で出力しても構いませんが、問題と無関係なユーザーディレクトリの内容は収集しないでください。
よくある誤認と対処方法は次のとおりです。
- アセット数が想定より多い:ジョブの前にシミュレータを消去していないか、複数のジョブが同じUDIDを再利用しています。
- テストで動画が見つからないことがある:投入直後に検索しており、写真ライブラリの更新完了を待っていません。
- 画像の幅と高さが逆になる:コードがピクセル寸法を読み取る際に、向きのメタデータを無視しています。
- ローカルでは成功するがCIでは失敗する:テストがコミットされていないファイル、デフォルトの権限、曖昧なdestinationに依存しています。
- アップロードのダイジェストが変わる:アプリがリソースを再エンコードしています。入力のダイジェストとアップロード本体のダイジェストを別々に記録してください。
ジョブ終了時にはシミュレータを停止します。現在のジョブが一時的に作成したデバイスであれば、そのまま削除します。長期間維持するデバイスでも、次のジョブの前に再度eraseを実行してください。写真ライブラリの内容を個別に削除する方法には依存しないようにします。最終的な確認項目は、フィクスチャのダイジェスト一致、UDIDの排他利用、システムバージョンの固定、権限状態の明示、取り込み件数を待機できること、結果バンドルのアーカイブに集約できます。これらを徹底すれば、メディア取り込みの失敗に対して、安定した入力と後から検証できる実行時の記録を確保できます。
よくある質問
simctl addmediaで実機の写真テストを置き換えられますか?
完全には置き換えられません。シミュレータでの検索、選択、読み込み、送信には適しますが、カメラ撮影、端末固有のコーデック、実ストレージの制約は実機確認が必要です。
CI実行ごとのメディア蓄積を防ぐ方法は?
ジョブごとに専用シミュレータを使い、投入前にshutdownとeraseを実行し、テスト終了後に一時デバイスを削除します。
独占型Apple Silicon物理ノードで次のビルドを実行
契約期間とノードのリージョンを選択し、Mac Mini M4、16GB RAM、256GB SSD、支払額(USD)を確認して注文を送信します。