テスト戦略

自動テストは、さまざまな方法でアプリの品質を向上させるのに役立ちます。たとえば、検証の実行、リグレッションの検出、互換性の検証に役立ちます。優れたテスト戦略では、自動テストを活用して、重要なメリットであるデベロッパーの生産性に集中できます。

テストに体系的なアプローチを採用し、インフラストラクチャを強化すると、チームの生産性が向上します。これにより、コードの動作に関するフィードバックがタイムリーに提供されます。優れたテスト戦略は次のようになります。

  • 問題をできるだけ早い段階で検出します。
  • 迅速に実行されます。
  • 修正が必要な場合に明確な指示を表示します。

このページでは、実装するテストの種類、テストを実行する場所、テストを実行する頻度を決定する際に役立つ情報を紹介します。

テスト ピラミッド

最新のアプリケーションでは、テストをサイズ別に分類できます。小規模なテストはコードのごく一部に絞り込まれるため、高速で信頼性に優れています。大規模なテストは範囲が広く、より複雑な設定が必要で、保守が困難です。ただし、大規模なテストは忠実度*が高く、一度に多くの問題を発見できます。

*忠実度とは、テスト ランタイム環境と本番環境の類似度を指します。

スコープごとのテスト数の分布は、通常はピラミッド状に可視化されます。
図 1. 通常、テスト数の分布はスコープ別にピラミッドで可視化されます。

ほとんどのアプリでは、小さなテストを多く、大きなテストを比較的少なくする必要があります。各カテゴリのテストの分布はピラミッド状に形成され、小規模なテストが多いほどベースを形成し、大きなテストの数がチップを形成しなくなります。

バグのコストを最小限に抑える

優れたテスト戦略では、バグの検出コストを最小限に抑えながら、デベロッパーの生産性を最大化します。

非効率的な戦略の例を検討しましょう。ここでは、サイズ別のテスト数がピラミッド状に整理されていません。大規模なエンドツーエンド テストが多すぎる、コンポーネント UI テストが少なすぎる:

多くのテストを手動で実行し、デバイステストは夜間にのみ実行する、トップヘビーな戦略。
図 2. 多くのテストを手動で実行し、デバイステストを夜間にしか実行しない、最も重視する戦略。

つまり、マージする前に実行されるテストが少なすぎるということです。バグがある場合、毎晩または毎週のエンドツーエンド テストが実行されるまで、テストで検出されないことがあります。

バグの特定と修正にかかる費用への影響と、テストの規模を小さくして頻度を高めることが重要である理由を検討することが重要です。

  • バグが単体テストで検出されると、通常は数分で修正されるため、費用は低くなります。
  • エンドツーエンド テストでも、同じバグを検出するまでに数日かかることがあります。複数のチームメンバーが集まり、全体的な生産性が低下し、リリースが遅れる可能性があります。このバグのコストは高くなります。

ただし、戦略がないよりは、非効率的なテスト戦略のほうがましです。バグが本番環境に到達すると、修正がユーザーのデバイスに反映されるまでに時間がかかります(数週間かかることもあります)。そのため、フィードバック ループが最も長く、最も費用がかかります。

スケーラブルなテスト戦略

テストのピラミッドは従来、次の 3 つのカテゴリに分かれています。

  • 単体テスト
  • 統合テスト
  • エンドツーエンド テスト。

ただし、これらのコンセプトには明確な定義がないため、チームは 5 つのレイヤなど、異なる方法でカテゴリを定義することもできます。

単体テスト、コンポーネント テスト、機能テスト、アプリケーション テスト、リリース候補テストのカテゴリを昇順に並べた 5 層のテストピラミッド。
図 3. 5 層のテスト ピラミッド。
  • 単体テストはホストマシンで実行され、Android フレームワークに依存しない単一の機能単位のロジックを検証します。
    • 例: 数学関数でオフバイワン エラーを確認する。
  • コンポーネント テストは、システム内の他のコンポーネントとは別に、モジュールやコンポーネントの機能または外観を検証します。単体テストとは異なり、コンポーネント テストの表領域は、個々のメソッドやクラスより上位の抽象化にまで広がっています。
  • 機能テストでは、2 つ以上の独立したコンポーネントまたはモジュールの相互作用を検証します。機能テストは規模が大きく複雑で、通常は機能レベルで実行されます。
  • アプリケーション テストでは、デプロイ可能なバイナリの形式でアプリケーション全体の機能を検証します。テスト対象システムとして、テストフックを含むことができるデバッグ可能なバイナリ(デベロッパー ビルドなど)を使用する大規模な統合テストです。
    • 例: 折りたたみ式デバイスの構成変更を確認する UI 動作テスト、ローカライズ テスト、アクセシビリティ テスト
  • リリース候補版テストは、リリースビルドの機能を検証します。アプリケーション バイナリが圧縮されて最適化されていることを除けば、アプリケーション テストと同様です。これらは、アプリを一般ユーザー アカウントや一般公開バックエンドに公開することなく、できるだけ本番環境に近い環境で実行される大規模なエンドツーエンドの統合テストです。

この分類では、忠実度、時間、範囲、分離レベルが考慮されます。複数のレイヤにわたってさまざまなタイプのテストを行うことができます。たとえば、アプリケーション テストレイヤには、動作テスト、スクリーンショット テスト、パフォーマンス テストを含めることができます。

範囲

ネットワーク アクセス

実行

ビルドの種類

ライフサイクル

単位

依存関係が最小限の単一のメソッドまたはクラス。

×

ローカル

デバッグ可能

統合前

コンポーネント

モジュールまたはコンポーネント レベル

複数のクラスを組み合わせる

×

ローカル
Robolectric
エミュレータ

デバッグ可能

統合前

機能

機能レベル

他のチームが所有するコンポーネントとの統合

モック版

ローカル
Robolectric
エミュレータ
デバイス

デバッグ可能

統合前

応用

アプリレベル

他のチームが所有する機能やサービスとの統合

モック
ステージング サーバー
本番環境サーバー

エミュレータ
デバイス

デバッグ可能

統合前
統合後

リリース候補版

アプリレベル

他のチームが所有する機能やサービスとの統合

本番環境サーバー

エミュレータ
デバイス

最小化されたリリースビルド

統合後
プレリリース版

テストカテゴリを決定する

経験則として、チームに適切なレベルのフィードバックを提供できるピラミッドの最下層を検討する必要があります。

たとえば、ログインフローの UI という機能の実装をテストする方法を考えてみましょう。テストする内容に応じて、次のカテゴリを選択します。

テスト対象

テスト対象の説明

テストカテゴリ

テストのタイプの例

フォーム バリデータ ロジック

メールアドレスを正規表現に照らし合わせて検証し、パスワード フィールドが入力されたことを確認するクラス。依存関係はありません。

単体テスト

ローカル JVM ユニットテスト

ログイン フォームの UI の動作

フォームが検証されたときにのみ有効になるボタンがあるフォーム

コンポーネント テスト

Robolectric で実行される UI 動作テスト

ログイン フォームの UI の外観

UX 仕様に準拠したフォーム

コンポーネント テスト

Compose プレビューのスクリーンショットのテスト

認証マネージャーとの統合

認証情報を認証マネージャーに送信し、さまざまなエラーを含むレスポンスを受信する UI。

機能テスト

フェイクを使用した JVM テスト

ログイン ダイアログ

ログインボタンが押されたときにログイン フォームを表示する画面。

アプリケーション テスト

Robolectric で実行される UI 動作テスト

クリティカル ユーザー ジャーニー: ログイン

ステージング サーバーでテスト アカウントを使用する完全なログインフロー

リリース候補版

デバイスで実行されるエンドツーエンドの Compose UI 動作テスト

あるものがどのカテゴリに属するかは、主観的な場合もあります。テストが優先度を上げたり下げたりする場合、インフラストラクチャの費用、不安定性、テスト時間の長さなど、その他の理由が考えられます。

テストカテゴリによってテストの種類が決定されるわけではなく、すべてのカテゴリですべての機能をテストする必要があるわけではありません。

手動テストもテスト戦略の一部に含めることができます。通常、QA チームはリリース候補版テストを実行しますが、他のステージに関与することもできます。たとえば、スクリプトのない機能のバグに関する探索的テストなどです。

テスト インフラストラクチャ

テスト戦略は、デベロッパーがテストを継続的に実行し、すべてのテストが合格することを保証するルールを適用できるように、インフラストラクチャとツールによってサポートされている必要があります。

テストをスコープ別に分類して、どのテストをいつ、どこで実行するかを定義できます。たとえば、次の 5 階層モデルがあるとします。

カテゴリ

環境(場所)

トリガー(タイミング)

単位

[ローカル][4]

すべての commit

コンポーネント

ローカル

すべての commit

機能

ローカルとエミュレータ

マージ前、変更をマージまたは送信する前

応用

ローカル、エミュレータ、スマートフォン 1 台、折りたたみ式デバイス 1 台

マージ後、変更のマージまたは送信後

リリース候補版

8 種類のスマートフォン、1 種類の折りたたみ式デバイス、1 種類のタブレット

リリース前

  • 単体テストコンポーネント テストは、新しい commit ごとに継続的インテグレーション システムで実行されますが、影響を受けるモジュールに対してのみ実行されます。
  • 変更をマージまたは送信する前に、すべての単体テスト、コンポーネント テスト機能テストが実行されます。
  • アプリケーション テストはマージ後に実行されます。
  • リリース候補版のテストは、スマートフォン、折りたたみ式デバイス、タブレットで夜間に実施されます。
  • リリース前に、多数のデバイスでリリース候補版テストが実行されます。

テストの数によって生産性が影響を受ける場合は、これらのルールが変更されることがあります。たとえば、テストを毎晩の頻度で移動すると、CI のビルドとテストの時間が短縮される可能性がありますが、フィードバック ループが長くなることがあります。