モバイルアプリのテスト:エミュレータ、シミュレータ、実機の使い分け
モバイルアプリをビルドし終えました。ラップトップ上では完璧に動作し、署名も済んでいます。ストアにリリースする準備は万端です。しかし、ここで認めなければならない厳しい現実があります。あなたのラップトップは、ユーザーのスマートフォンとはまったく別物だということです。
ユーザーは、何百もの異なるデバイスであなたのアプリを実行します。画面サイズ、OSバージョン、バッテリー残量、ネットワーク環境、ハードウェア構成はさまざまです。開発環境では決して発生しないバグが、特定の機種でアプリをクラッシュさせる可能性があります。一度のリリース失敗で、ストアの評価は急落し、一夜にしてユーザーを失うこともあり得ます。
モバイルアプリのテストは、もはや選択肢ではありません。しかし、どのようにテストし、どこでテストを実行するかによって、リリース前に得られる確信の度合いは大きく変わります。
重要となるテストのレイヤー
モバイルテストは単一のものではありません。それは複数のレイヤーが積み重なった構造であり、各レイヤーが異なる種類の問題を異なる速度で捉えます。
単体テストは最下層に位置します。これらは、単一の振る舞いが正しく動作することを検証します。モバイルの文脈では、意味のあるエントリーポイントからテストすることを意味します。ユーザーがボタンをタップしたとき、ViewModelは正しく反応するか?特定の入力に対して、ユースケースは正しい出力を返すか?内部の実装詳細をテストするのではなく、観測可能な振る舞いをテストします。単体テストは高速で、多くの場合ミリ秒単位で完了します。パイプライン内でコードが変更されるたびに、エミュレータやシミュレータを使ってテストをトリガーできます。実機は必要ありません。
結合テストは一つ上のレイヤーです。コンポーネントがどのように連携するかをチェックします。APIからのデータが実際に画面に正しく表示されるか?ユーザーログイン後、ローカルストレージは正しく動作するか?これらのテストには、実機に近い環境が必要です。統合する対象に応じて、エミュレータ、シミュレータ、または実機のいずれも使用できます。
UIテストは最上位に位置します。これらは実際のユーザー操作(ボタンタップ、フォーム入力、リストのスクロール、期待される要素が画面に表示されることの確認)をシミュレートします。これらのテストは、ユーザーが実際に体験することに最も近いものです。また、最も低速で、最も壊れやすいテストでもあります。小さなレイアウト変更で簡単に失敗します。しかし、これらがパスすれば、アプリがユーザー視点で機能していることがわかります。
各レイヤーにはそれぞれの役割があります。単体テストは迅速なフィードバックを提供します。結合テストは配線の問題を発見します。UIテストはユーザー体験を検証します。これら3つすべてが必要ですが、すべてのトリガーで全てを実行する必要はありません。
エミュレータとシミュレータ:高速だが完璧ではない
エミュレータ(Android)とシミュレータ(iOS)は、モバイルテストの主力です。これらは無料で、CI/CDパイプラインで簡単に起動でき、ほとんどのロジックやレイアウトのチェックには十分です。
単体テスト、結合テスト、さらにはUIテストもこれらで実行できます。起動が速く、異なるOSバージョンをサポートし、様々な画面サイズをシミュレートできます。日々の開発や内部ビルドには、通常これで十分です。
しかし、盲点もあります。エミュレータやシミュレータは、実際のデバイスの動作を再現しません。パフォーマンス、バッテリー消費、センサーの応答、セルラーネットワーク上の動作はすべて異なります。エミュレータで完璧にパスしたテストが、タイミング、メモリプレッシャー、またはハードウェア固有の問題のために、実機で失敗することがあります。
これを実践的なものにするために、Androidエミュレータを作成し、起動を待ち、インストルメント化テストを実行し、結果を収集するGitHub Actionsのジョブを以下に示します。
name: Android Instrumented Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Create and start emulator
run: |
echo "no" | avdmanager create avd -n testDevice -k "system-images;android-33;google_apis;x86_64" --force
$ANDROID_HOME/emulator/emulator -avd testDevice -no-window -no-audio &
- name: Wait for emulator to boot
run: |
adb wait-for-device
adb shell settings put global window_animation_scale 0.0
adb shell settings put global transition_animation_scale 0.0
adb shell settings put global animator_duration_scale 0.0
- name: Run instrumented tests
run: ./gradlew connectedCheck
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: app/build/reports/androidTests/connected/
エミュレータだけでテストしている場合、不完全な情報でリリースしていることになります。
デバイスファーム:スケールする実機テスト
ここで登場するのがデバイスファームです。デバイスファームとは、データセンターに設置された実際のスマートフォンやタブレットへのアクセスを提供するサービスです。アプリをアップロードし、複数のデバイスで同時にテストを実行し、各デバイスで何が成功し、何が壊れたかを示すレポートを取得します。
一般的な選択肢としては、AndroidとiOSの両方に対応するFirebase Test Labや、AWS Device Farmがあります。これらのサービスは、さまざまなデバイスモデル、OSバージョン、画面サイズをサポートしています。CI/CDパイプラインに直接統合できます。ビルドが完了するたびに、アプリがストアにリリースされる前に、パイプラインが実機でのテストをトリガーします。
デバイスファームは、エミュレータでは見逃される問題を発見します。特定のハードウェアでのクラッシュ、異常な画面比率でのレイアウト問題、古いデバイスでのパフォーマンス低下などです。また、各テスト実行のスクリーンショットとログも提供されるため、障害の診断が容易になります。
しかし、デバイスファームは無料ではありません。テスト実行ごとにコストがかかり、エミュレータテストよりも完了までに時間がかかります。すべてのビルドをデバイスファームで実行するのは得策ではありません。遅くて高コストになります。
いつ何を使うべきか
この判断は、イデオロギーではなく実用性に基づきます。開発中の迅速なフィードバックや内部ビルドにはエミュレータとシミュレータを使用します。リリース前には戦略的にデバイスファームを使用します。
以下のフローチャートは、速度と忠実度のニーズに基づいて、各テストタイプに推奨される環境をまとめたものです。
シンプルな経験則を以下に示します。
- すべてのコミット: エミュレータまたはシミュレータで単体テストを実行する。
- すべてのプルリクエスト: エミュレータまたはシミュレータで単体テストと結合テストを実行する。
- 段階的ロールアウトまたはフェーズドリリースの前: 代表的なデバイスセットをカバーするデバイスファームで、完全なテストスイートを実行する。
重要なのは、テストの強度をリスクに合わせることです。内部機能の小さなバグ修正に、フルデバイスファーム実行は必要ありません。何千人ものユーザーに届くリリースには、絶対に必要です。
ストア提出前の実用的なチェックリスト
アプリストアのダッシュボードで送信ボタンを押す前に、以下のクイックチェックリストを実行してください。
- 対象OSバージョンのエミュレータまたはシミュレータで単体テストがパスしていること。
- 主要なユーザーフロー(ログイン、データ表示、ナビゲーション)の結合テストがパスしていること。
- 少なくとも1つのエミュレータと1つの実機でUIテストがパスしていること。
- 分析でユーザーが実際に使用している上位5~10のデバイスモデルで、デバイスファームテストがパスしていること。
- デバイスファーム実行からのクラッシュまたはANR(アプリケーションが応答しない)レポートがないこと。
- デバイスファームからのスクリーンショットが、異なる画面サイズで期待されるレイアウトと一致していること。
このチェックリストは網羅的ではありませんが、本番環境にすり抜ける最も一般的な問題を捉えます。
具体的な教訓
エミュレータだけでテストすることは、駐車場で車を試乗するようなものです。ステアリングが効くことはわかりますが、高速道路でのハンドリングはわかりません。デバイスファームは、高速道路のデータを提供します。スピードが必要な場合はエミュレータを、確信が必要な場合はデバイスファームを使用してください。両方をパイプラインに自動化すれば、バグを減らしてリリースでき、夜も安心して眠れます。