ViewModel と LiveData をテストする

1. 始める前に

これまでの Codelab では、ビジネス ロジックを処理するために ViewModel を使用する方法と、リアクティブ UI のために LiveData を使用する方法を学びました。この Codelab では、ViewModel コードが適切に動作していることを確認するために単体テストを作成する方法を学びます。

前提条件

  • Android Studio でテスト ディレクトリを作成している
  • Android Studio で単体テストとインストルメンテーション テストを作成している
  • Android プロジェクトに Gradle の依存関係を追加している

学習内容

  • ViewModelLiveData の単体テストを作成する方法。

必要なもの

  • Android Studio がインストールされているパソコン
  • Cupcake アプリの解答コード。

この Codelab のスターター コードをダウンロードする

この Codelab では、以前の解答コードの Cupcake アプリにインストルメンテーション テストを追加します。

この Codelab のコードを取得して Android Studio で開く手順は以下のとおりです。

コードを取得する

  1. 指定された URL をクリックします。プロジェクトの GitHub ページがブラウザで開きます。
  2. ブランチ名と Codelab で指定したブランチ名が一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。

fe29aa9112862a93.png

  1. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。

5b0a76c50478a73f.png

  1. ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。

a065e3d575fe607b.png

注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。

4f3b1e628c7695f1.png

  1. ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開くまで待ちます。
  4. 実行ボタン 11c34fc5e516fb1c.png をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。

2. スターター アプリの概要

Cupcake アプリは、カップケーキの数量について 3 つのオプションを備えた注文画面を表示するホーム画面で構成されています。オプションをクリックするとフレーバーを選択する画面になり、注文品の受け取り日を選択する画面になります。その後、注文を別のアプリに送信できます。注文はどの段階でもキャンセルできます。

3.単体テスト ディレクトリを作成する

これまでの Codelab で行ったように、Cupcake アプリの単体テスト ディレクトリを作成します。

4. 単体テストクラスを作成する

ViewModelTests.kt という新しいクラスを作成します。

5. 必要な依存関係を追加する

プロジェクトに次の依存関係を追加します。

testImplementation 'junit:junit:4.+'
testImplementation 'androidx.arch.core:core-testing:2.1.0'

次に、プロジェクトを同期します。

6. ViewModel テストを作成する

簡単なテストから始めましょう。デバイスまたはエミュレータでアプリを操作するとき最初にすることは、カップケーキの数量を選択することです。そこで、まず OrderViewModelsetQuantity() メソッドをテストし、quantity LiveData オブジェクトの値を確認します。

ここでテストする quantity 変数は LiveData のインスタンスです。LiveData オブジェクトをテストするには追加の手順が必要です。ここで、追加した依存関係が機能します。値が変更されるとすぐに UI を更新するために、LiveData を使用します。UI は「メインスレッド」というもので動作します。スレッド化と同時実行についてよく知らなくても問題ありません。他の Codelab で詳しく説明します。当面の間、Android アプリのコンテキストでは、メインスレッドを UI スレッドとして考えてください。ユーザーに UI を表示するコードは、このスレッドで実行されます。特に指定がない限り、単体テストはすべてがメインスレッドで実行されることを想定しています。ただし、LiveData オブジェクトはメインスレッドにアクセスできないため、LiveData オブジェクトがメインスレッドを呼び出してはならないことを明示的に記述する必要があります。

  1. LiveData オブジェクトがメインスレッドを呼び出さないように指定するには、LiveData オブジェクトをテストするたびに特定のテストルールを提供する必要があります。
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
  1. これで、quantity_twelve_cupcakes() という関数を作成できるようになりました。メソッドで、OrderViewModel. のインスタンスを作成します。
  2. このテストでは、setQuantity が呼び出されたときに、OrderViewModelquantity オブジェクトが更新されることを確認します。ただし、メソッドを呼び出す前、または OrderViewModel のデータを扱う前に、変更を出力するには LiveData オブジェクトの値をテストするときにオブジェクトを監視する必要がある点に注意することが重要です。これを行う簡単な方法は、observeForever メソッドを使用することです。quantity オブジェクトで observeForever メソッドを呼び出します。このメソッドにはラムダ式が必要ですが、ラムダ式は空のままにしておくことができます。
  3. 次に、パラメータとして 12 を渡して setQuantity() メソッドを呼び出します。
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
  1. quantity オブジェクトの値は 12 であると推測できます。なお、LiveData オブジェクトは値そのものではありません。値は、value というプロパティに含まれています。次のアサーションを作成します。
assertEquals(12, viewModel.quantity.value)

テストは次のようになります。

@Test
fun quantity_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.quantity.observeForever {}
   viewModel.setQuantity(12)
   assertEquals(12, viewModel.quantity.value)
}

テストを実行します。これで、最新の Android 開発に欠かせないスキルである、最初の LiveData 単体テストを作成できました。このテストではビジネス ロジックはあまりテストされないため、もう少し複雑なテストを作成しましょう。

OrderViewModel の主な機能として、注文価格の計算があります。これは、カップケーキの数量を選択したときと、受け取り日を選択したときに行われます。価格計算はプライベート メソッドで行われるため、テストでこのメソッドを直接呼び出すことはできません。このメソッドは、OrderViewModel の他のメソッドだけが呼び出せます。こうしたメソッドは公開されているため、価格計算をトリガーするために呼び出して、価格の値が期待どおりであることを確認できます。

おすすめの方法

カップケーキの数量が選択されたときと、日付が選択されたときに、価格が更新されます。いずれもテストする必要がありますが、通常は 1 つの機能だけをテストすることをおすすめします。そのため、テストごとに別々のメソッドを作成します(数量が更新されたときに価格をテストする関数と、日付が更新されたときに価格をテストする関数)。あるテストが失敗したために別のテストが失敗するということがあってはなりません。

  1. price_twelve_cupcakes() というメソッドを作成し、テストとしてアノテーションを付けます。
  2. このメソッドで OrderViewModel のインスタンスを作成し、パラメータとして 12 を渡して setQuantity() メソッドを呼び出します。
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
  1. OrderViewModelPRICE_PER_CUPCAKE を見ると、カップケーキは 1 つ $2.00 であることがわかります。また、ViewModel が初期化されるたびに resetOrder() が呼び出され、このメソッドでは、デフォルトの日付は今日の日付であり、PRICE_FOR_SAME_DAY_PICKUP は $3.00 であることがわかります。そのため、12 * 2 + 3 = 27 となります。12 個のカップケーキを選択した後の price 変数の値は、$27.00 になると予想されます。そこで、期待値である $27.00 が price LiveData オブジェクトの値に等しいというアサーションを作成しましょう。
assertEquals("$27.00", viewModel.price.value)

テストを実行します。

これは失敗するはずです。

17c8a24e4d7d635d.png

テスト結果では、実際の値は null となっています。これには理由があります。OrderViewModelprice 変数を見てみると、次のようになっています。

val price: LiveData<String> = Transformations.map(_price) {
   // Format the price into the local currency and return this as LiveData<String>
   NumberFormat.getCurrencyInstance().format(it)
}

この例は、テストで LiveData を監視すべき理由を示しています。price の値は、Transformation を使用して設定されます。このコードは基本的に、price に割り当てられた値を受け取って通貨形式に変換します。そのため、手動で行う必要はありません。ただし、このコードには他の意味もあります。LiveData オブジェクトを変換する際、どうしても必要な場合を除き、このコードは呼び出されません。そうすることでモバイル デバイスのリソースを節約します。このコードは、オブジェクトの変更を監視する場合にのみ呼び出されます。もちろん、これはアプリで行われますが、テストでも同じことを行う必要があります。

  1. テストメソッドで、数量を設定する前に次の行を追加します。
viewModel.price.observeForever {}

テストは次のようになります。

@Test
fun price_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.price.observeForever {}
   viewModel.setQuantity(12)
   assertEquals("$27.00", viewModel.price.value)
}

これで、テストを実行すると合格するはずです。

7. 解答コード

8. 完了

この Codelab では次のことを行いました。

  • LiveData テストの設定方法を学習しました。
  • LiveData 自体をテストする方法を学習しました。
  • 変換された LiveData をテストする方法を学習しました。
  • 単体テストで LiveData を監視する方法を学習しました。