この Codelab は、Kotlin での高度な Android 開発コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できますが、これは必須ではありません。すべてのコース Codelab は Kotlin での Codelab の高度な Codelab のランディング ページに掲載されています。
はじめに
初めてアプリ用の機能を実装したとき、コードを実行して意図したとおりに動作していることを確認します。手動テストであっても、テストを実施した。機能の追加や更新を続けて、コードの実行と動作の検証も続けられたのではないでしょうか。しかし、この作業は毎回手間がかかり、間違いが起こりやすいだけでなく、規模拡大にもつながりません。
コンピュータはスケーリングと自動化に長けている!そのため、大小さまざまなデベロッパーが、自動テストを作成します。これは、ソフトウェアによって実行されるテストであり、コードの動作を確認するためにアプリを手動で操作する必要はありません。
この一連の Codelab で学ぶのは、実世界のアプリ用にテストのコレクション(テストスイート)を作成する方法です。
この最初の Codelab では、Android でのテストの基本について説明します。最初のテストを作成し、LiveData
と ViewModel
をテストする方法を学びます。
前提となる知識
以下について把握しておく必要があります。
- Kotlin プログラミング言語
- コア Android Jetpack ライブラリ:
ViewModel
、LiveData
- アプリケーション アーキテクチャ。アプリ アーキテクチャ ガイドと Android の基礎の Codelab のパターンに従います。
学習内容
次のトピックについて学びます。
- Android で単体テストを作成して実行する方法
- テスト駆動開発の使用方法
- インストルメンテーション テストとローカルテストの選び方
以下のライブラリとコードのコンセプトについて学びます。
演習内容
- Android でローカルテストとインストルメンテーション テストの両方を設定、実行、解釈します。
- JUnit4 と Hamcrest を使用して Android で単体テストを作成します。
- 単純な
LiveData
テストとViewModel
テストを作成します。
このシリーズの Codelab では、ToDo リスト アプリを使用します。このアプリを使用すると、完了すべきタスクを書き留めて、リストで確認できます。その後、完了としてマーク、フィルタ、削除することができます。
このアプリは Kotlin で記述されており、複数の画面を備え、Jetpack コンポーネントを使用し、アプリ アーキテクチャ ガイドのアーキテクチャに沿っています。このアプリをテストする方法を学ぶことで、同じライブラリとアーキテクチャを使用するアプリをテストできるようになります。
まず、コードをダウンロードします。
または、コードの GitHub リポジトリのクローンを作成することもできます。
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
このタスクでは、アプリを実行してコードベースを確認します。
ステップ 1: サンプルアプリを実行する
ToDo アプリをダウンロードしたら、Android Studio で開いて実行します。コンパイルできるはずです。アプリを探索する手順は次のとおりです。
- プラスのフローティング操作ボタンで新しいタスクを作成します。はじめにタイトルを入力し、次にタスクに関する追加情報を入力します。緑色のチェックマークの FAB を使用して保存します。
- タスクのリストで、先ほど完了したタスクのタイトルをクリックし、そのタスクの詳細画面で説明の残りを確認します。
- リストまたは詳細画面で、そのタスクのチェックボックスをオンにして、ステータスを [Completed] に設定します。
- タスク画面に戻り、フィルタ メニューを開き、ステータスを [アクティブ] と [完了] でフィルタします。
- ナビゲーション ドロワーを開き、[統計情報] をクリックします。
- 概要画面に戻り、ナビゲーション ドロワー メニューで [完了] を選択して、ステータスが [完了] のタスクをすべて削除します。
ステップ 2: サンプルアプリのコードを調べる
TO-DO アプリは、一般的なアーキテクチャ ブループリントのテストとアーキテクチャのサンプル(リアクティブ アーキテクチャ バージョンのサンプル)に基づいています。アプリは、アプリ アーキテクチャ ガイドのアーキテクチャに沿って実行されます。Fragment、リポジトリ、Room とともに ViewModel を使用する。下記の例のいずれかに精通している場合は、このアプリのアーキテクチャは類似しています。
- Room とビュー Codelab
- Android Kotlin の基礎トレーニング Codelab
- 高度な Android トレーニング Codelab
- Android Sunflower サンプル
- Kotlin Udacity による Android アプリの開発トレーニング コース
どのレイヤでも、ロジックについて深く理解することよりも、アプリの一般的なアーキテクチャを理解することが重要です。
表示されるパッケージの概要は次のとおりです。
パッケージ: | |
| タスクの追加または編集画面: タスクを追加または編集するための UI レイヤコードです。 |
| データレイヤー: タスクのデータレイヤーに適用されます。これには、データベース、ネットワーク、リポジトリのコードが含まれます。 |
| 統計情報の画面: 統計情報画面の UI レイヤコードです。 |
| タスク詳細画面: 単一タスクの UI レイヤコード。 |
| タスク画面: すべてのタスクのリストの UI レイヤコード。 |
| ユーティリティ クラス: アプリのさまざまな部分で使用される共有クラス(複数の画面で使用されるスワイプ更新レイアウトなど)。 |
データレイヤー(.data)
このアプリには、シミュレートされたネットワーキング レイヤ(リモート パッケージ)とデータベース レイヤ(ローカル パッケージ)が含まれています。わかりやすくするため、このプロジェクトでは、実際のネットワーク リクエストを作成するのではなく、遅延を考慮して HashMap
のみでネットワーク レイヤをシミュレートします。
DefaultTasksRepository
は、ネットワーク レイヤとデータベース レイヤの間で調整または調整を行い、UI レイヤにデータを返します。
UI レイヤ(.addedittask、.statistics、.taskdetail、.tasks)
各 UI レイヤ パッケージには、フラグメントとビューモデル、UI に必要な他のクラス(タスクリストのアダプターなど)が含まれています。TaskActivity
は、すべてのフラグメントを含むアクティビティです。
ナビゲーション
アプリのナビゲーションは、Navigation コンポーネントによって制御されます。これは nav_graph.xml
ファイルで定義されます。ナビゲーションは、Event
クラスを使用してビューモデルでトリガーされます。ビューモデルは、渡す引数も決定します。フラグメントは Event
を監視し、画面間の実際のナビゲーションを行います。
このタスクでは、最初のテストを実行します。
- Android Studio で [Project] ペインを開き、次の 3 つのフォルダを見つけます。
com.example.android.architecture.blueprints.todoapp
com.example.android.architecture.blueprints.todoapp (androidTest)
com.example.android.architecture.blueprints.todoapp (test)
これらのフォルダは、ソースセットと呼ばれます。ソースセットとは、アプリのソースコードを含むフォルダです。緑のセット(androidTest と test)のソースセットに、テストが含まれています。新しい Android プロジェクトを作成すると、デフォルトで次の 3 つのソースセットが取得されます。それらは次のとおりです。
main
: アプリコードが含まれます。このコードは、ビルド可能なさまざまなバージョンのアプリ(ビルド バリアント)で共有されます。androidTest
: インストルメンテーション テストと呼ばれるテストが含まれます。test
: ローカルテストと呼ばれるテストが含まれます。
ローカルテストとインストルメンテーション テストの違いは、実行方法にあります。
ローカルテスト(test
ソースセット)
これらのテストは開発マシンの JVM 上でローカルに実行されるため、エミュレータや物理デバイスは必要ありません。そのため、動作は速くなりますが、忠実度は低くなります。つまり、現実世界と同じように動作しにくくなります。
Android Studio では、ローカルテストは緑と赤の三角形のアイコンで表されます。
インストルメンテーション テスト(androidTest
ソースセット)
これらのテストは、実際の Android デバイスまたはエミュレートされた Android デバイスで実行されるため、現実世界で何が起こるかが反映されますが、はるかに遅くなります。
Android Studio のインストルメンテーション テストは、緑色と赤色の三角形のアイコンが付いた Android で表されます。
ステップ 1: ローカルテストを実行する
- ExampleUnitTest.kt ファイルが見つかるまで
test
フォルダを開きます。 - 右クリックして、[Run ExampleUnitTest] を選択します。
画面の下部にある [Run] ウィンドウに次の出力が表示されます。
- 緑色のチェックマークが表示され、テスト結果を展開して、
addition_isCorrect
という 1 つのテストに合格したことを確認します。追加機能が意図したとおりに機能することを知っておいてください。
ステップ 2: テストに失敗する
以下は、実行したテストです。
ExampleUnitTest.kt
// A test class is just a normal class
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
// Here you are checking that 4 is the same as 2+2
assertEquals(4, 2 + 2)
}
}
テスト
- は、テスト ソースセットのいずれかのクラスである。
@Test
アノテーションで始まる関数が含まれている(各関数は 1 つのテストです)。- u8sually には、アサーション ステートメントを含めます。
Android は、テスト ライブラリ JUnit をテストに使用します(この Codelab JUnit4 に含まれています)。アサーションと @Test
アノテーションはどちらも JUnit から取得されます。
アサーションはテストの中核です。これは、コードまたはアプリが期待どおりに動作することをチェックするコードステートメントです。この場合のアサーションは assertEquals(4, 2 + 2)
であり、4 が 2 + 2 に等しいことを確認します。
失敗したテストの内容を確認するには、簡単に失敗できるアサーションを追加します。3 が 1 + 1 に等しいかどうかをチェックします。
addition_isCorrect
テストにassertEquals(3, 1 + 1)
を追加します。
ExampleUnitTest.kt
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
assertEquals(3, 1 + 1) // This should fail
}
}
- テストを実行します。
- テスト結果では、テストの横に [X] が表示されます。
- また、次の点にもご注意ください。
- アサーションが 1 回失敗すると、テスト全体が失敗します。
- 期待値(3)と実際に計算された値(2)が示されます。
- 失敗したアサーションの
(ExampleUnitTest.kt:16)
の行に移動します。
ステップ 3: インストルメンテーション テストを実行する
インストルメンテーション テストは androidTest
ソースセットに含まれています。
androidTest
ソースセットを開きます。ExampleInstrumentedTest
というテストを実行します。
ExampleInstrumentedTest
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.android.architecture.blueprints.reactive",
appContext.packageName)
}
}
ローカルテストとは異なり、このテストはデバイス(以下の例では、エミュレートされた Google Pixel 2)で実施します。
デバイスが接続されている場合、またはエミュレータが稼働している場合、エミュレータでテストが実行されます。
このタスクでは、getActiveAndCompleteStats
のテストを作成します。このアプリは、アプリのアクティブ タスクと完了済みのタスクの割合(%)を計算します。これらの数値はアプリの統計画面で確認できます。
ステップ 1: テストクラスを作成する
main
ソースセットのtodoapp.statistics
でStatisticsUtils.kt
を開きます。getActiveAndCompletedStats
関数を見つけます。
StatisticsUtils.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)
getActiveAndCompletedStats
関数はタスクのリストを受け入れ、StatsResult
を返します。StatsResult
は、2 つの数値、完了したタスクの割合、およびアクティブなタスクの割合を含むデータクラスです。
Android Studio には、テスト用のスタブを生成するツールが用意されています。
getActiveAndCompletedStats
を右クリックして [Generate &ttest] を選択します。
[Create Test] ダイアログが開きます。
- [Class name:] を
StatisticsUtilsTest
に変更します(StatisticsUtilsKtTest
ではなく、テストクラス名に KT は適用しない)。 - 他の項目はデフォルト値のままにしておきます。JUnit 4 が適切なテスト ライブラリです。宛先パッケージが正しい(
StatisticsUtils
クラスの場所を反映している)ため、チェックボックスをチェックする必要はありません(余分なコードが生成されますが、テストはゼロから作成します)。 - [OK] を押します。
[Choose Destination Directory] ダイアログが開きます。
関数の計算はローカルで行うため、ローカルテストを作成します。これは Android 固有のコードを含んでいないためです。そのため、実際のデバイスまたはエミュレートしたデバイスで実行する必要はありません。
- ローカルテストを作成するため、
test
ディレクトリ(androidTest
ではない)を選択します。 - [OK] をクリックします。
- 生成された
StatisticsUtilsTest
クラスがtest/statistics/
にあります。
ステップ 2: 最初のテスト関数を作成する
以下を確認するテストを作成します。
- 完了したタスクとアクティブなタスクが 1 つもない場合、
- アクティブなテストの割合が 100% であること
- 完了したタスクの割合は 0% です。
StatisticsUtilsTest
を開きます。getActiveAndCompletedStats_noCompleted_returnsHundredZero
という名前の関数を作成します。
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- 関数名の上に
@Test
アノテーションを追加して、テストであることを示します。 - タスクリストを作成する。
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- これらのタスクで
getActiveAndCompletedStats
を呼び出します。
// Call your function
val result = getActiveAndCompletedStats(tasks)
- アサーションを使用して、
result
が想定どおりであることを確認します。
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
完全なコードは次のとおりです。
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
}
}
- テストを実行します(
StatisticsUtilsTest
を右クリックし、[Run] を選択します)。
次の条件を満たす必要があります。
ステップ 3: Hamcrest 依存関係を追加する
テストはコードが行うことに関するドキュメントとして機能するため、人が読める形式にすると便利です。次の 2 つのアサーションを比較します。
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
2 番目のアサーションは人間の文によく似ています。Hamcrest というアサーション フレームワークを使用して記述されています。読みやすいアサーションを作成するためのもう 1 つのツールは、Truth ライブラリです。この Codelab では、Hamcrest を使用してアサーションを作成します。
build.grade (Module: app)
を開き、次の依存関係を追加します。
app/build.gradle
dependencies {
// Other dependencies
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}
通常は、依存関係を追加する際に implementation
を使用しますが、ここでは testImplementation
を使用しています。アプリを公開する準備ができたら、アプリのテストコードや依存関係に APK のサイズを拡大するのは避けましょう。Gradle 設定を使用すると、ライブラリをメインコードに含めるかテストコードに含めるかを指定できます。最も一般的な構成は次のとおりです。
implementation
- 依存関係は、テスト ソースセットを含むすべてのソースセットで使用可能になります。testImplementation
- 依存関係はテスト ソースセットでのみ使用できます。androidTestImplementation
- 依存関係はandroidTest
ソースセットでのみ使用できます。
どの構成を使用するかは、依存関係を使用できる場所を定義します。次のように記述します。
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
そのため、Hamcrest はテスト ソースセットでのみ利用可能です。また、最終的なアプリには Hamcrest が含まれなくなります。
ステップ 4: Hamcrest を使用してアサーションを作成する
assertEquals
の代わりに Hamcrest のassertThat
を使用するようにgetActiveAndCompletedStats_noCompleted_returnsHundredZero()
テストを更新します。
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
メッセージが表示されたら、インポート import org.hamcrest.Matchers.`is`
を使用できます。
最終的なテストは以下のコードのようになります。
StatisticsUtilsTest.kt
import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
// Create an active tasks (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
- 更新したテストを実行して、引き続き動作することを確認してください。
この Codelab では、Hacrest について詳しく解説しているわけではありません。詳しくは、公式チュートリアルをご確認ください。
これは演習を行うためのオプション タスクです。
このタスクでは、JUnit と Hamcrest を使用してさらにテストを作成します。また、テスト駆動開発のプラクティスから派生した戦略を使用してテストを作成します。テスト駆動型開発(TDD)はプログラミングの思想の集まりであり、最初に機能コードを記述するのではなく、テストを最初に記述します。次に、テストに合格することを目標に、機能コードを記述します。
ステップ 1: テストを作成する
通常のタスクリストがある場合に使用するテストを作成します。
- 完了したタスクが 1 つあり、アクティブなタスクがない場合、
activeTasks
の割合は0f
になり、完了したタスクの割合は100f
になります。 - 完了したタスクが 2 つ、有効なタスクが 3 つある場合、完了した割合は
40f
、有効の割合は60f
になります。
ステップ 2: バグのテストを作成する
記述された getActiveAndCompletedStats
のコードにはバグがあります。リストが空または null の場合、適切に処理されないことに注意してください。どちらの場合も、両方の割合はゼロになります。
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
コードを修正してテストを作成するには、テスト駆動型開発を使用します。テスト駆動開発は以下の手順を行います。
- 「した場合」「タイミング」「タイミング」「次の構文」で、規則に従った名前でテストを記述します。
- テストが失敗することを確認します。
- テストに合格するには、最小限のコードを記述する必要があります。
- すべてのテストでこの手順を繰り返します。
バグの修正から始めるのではなく、最初にテストを作成します。今後、このようなバグの再発を防止するためのテストが実施されていることを確認できます。
- 空のリスト(
emptyList()
)がある場合は、両方の割合を 0f にします。 - タスクの読み込み中にエラーが発生しました。リストは
null
になり、両方の割合は 0f になります。 - テストを実行して、失敗することを確認します。
ステップ 3: バグの修正
テストが完了したら、バグを修正します。
tasks
がnull
または空の場合に0f
を返すようにgetActiveAndCompletedStats
のバグを修正します。
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
return if (tasks == null || tasks.isEmpty()) {
StatsResult(0f, 0f)
} else {
val totalTasks = tasks.size
val numberOfActiveTasks = tasks.count { it.isActive }
StatsResult(
activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
)
}
}
- テストを再度実行し、すべてのテストに合格したことを確認します。
TDD に沿ってテストを先に記述することで、次のことを確認できました。
- 新しい機能には常に関連するテストがあります。そのため、テストはコードの動作に関するドキュメントとして機能します。
- テストでは、正しい結果を確認し、すでに確認したバグから保護します。
解決策: 追加のテストを作成する
すべてのテストと対応する機能コードは次のとおりです。
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
val tasks = listOf(
Task("title", "desc", isCompleted = false)
)
// When the list of tasks is computed with an active task
val result = getActiveAndCompletedStats(tasks)
// Then the percentages are 100 and 0
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
@Test
fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
val tasks = listOf(
Task("title", "desc", isCompleted = true)
)
// When the list of tasks is computed with a completed task
val result = getActiveAndCompletedStats(tasks)
// Then the percentages are 0 and 100
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(100f))
}
@Test
fun getActiveAndCompletedStats_both_returnsFortySixty() {
// Given 3 completed tasks and 2 active tasks
val tasks = listOf(
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = false),
Task("title", "desc", isCompleted = false)
)
// When the list of tasks is computed
val result = getActiveAndCompletedStats(tasks)
// Then the result is 40-60
assertThat(result.activeTasksPercent, `is`(40f))
assertThat(result.completedTasksPercent, `is`(60f))
}
@Test
fun getActiveAndCompletedStats_error_returnsZeros() {
// When there's an error loading stats
val result = getActiveAndCompletedStats(null)
// Both active and completed tasks are 0
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(0f))
}
@Test
fun getActiveAndCompletedStats_empty_returnsZeros() {
// When there are no tasks
val result = getActiveAndCompletedStats(emptyList())
// Both active and completed tasks are 0
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
StatisticsUtils.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
return if (tasks == null || tasks.isEmpty()) {
StatsResult(0f, 0f)
} else {
val totalTasks = tasks.size
val numberOfActiveTasks = tasks.count { it.isActive }
StatsResult(
activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
)
}
}
テストの作成と実行の基礎を習得しました。次は、基本的な ViewModel
テストと LiveData
テストの作成方法について学びます。
Codelab の残りの部分では、ほとんどのアプリで共通する 2 つの Android クラス(ViewModel
および LiveData
)のテストを作成する方法を学習します。
まず、TasksViewModel
のテストを作成します。
ビューモデル内にすべてのロジックがあり、リポジトリ コードに依存しないテストに焦点を当てます。リポジトリ コードには非同期コード、データベース、ネットワーク呼び出しが含まれているため、テストが複雑になります。当面はそれを避け、リポジトリ内の項目を直接テストしない ViewModel 機能のテストを作成することに専念します。
このテストでは、addNewTask
メソッドを呼び出すと、新しいタスク ウィンドウを開く Event
が発生することを確認します。こちらがテストするアプリコードです。
TasksViewModel.kt
fun addNewTask() {
_newTaskEvent.value = Event(Unit)
}
ステップ 1: TasksViewModelTest クラスを作成する
StatisticsUtilTest
と同じ手順で、TasksViewModelTest
のテストファイルを作成します。
tasks
パッケージ(TasksViewModel.
)で、テストするクラスを開きます。- コードで、クラス名
TasksViewModel
-> [Generate] -> [Test] を右クリックします。
- [Create Test] 画面で、[OK] をクリックして同意します(デフォルトの設定を変更する必要はありません)。
- [Choose Destination Directory] ダイアログで [test] ディレクトリを選択します。
ステップ 2: ViewModel テストを作成する
このステップでは、ビューモデルのテストを追加して、addNewTask
メソッドを呼び出すと、新しいタスク ウィンドウを開くための Event
が呼び出されます。
addNewTask_setsNewTaskEvent
という新しいテストを作成します。
TasksViewModelTest.kt
class TasksViewModelTest {
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh TasksViewModel
// When adding a new task
// Then the new task event is triggered
}
}
アプリケーションのコンテキストについて
テストする TasksViewModel
のインスタンスを作成する場合、そのコンストラクタにはアプリケーション コンテキストが必要です。ただし、このテストでは、アクティビティと UI とフラグメントを含む完全なアプリケーションを作成するわけではないため、アプリケーションのコンテキストを取得するには、どうすればよいでしょうか。
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(???)
AndroidX Test Library には、テスト用のアクティビティやアクティビティなどのコンポーネントのバージョンを提供するクラスやメソッドが含まれています。シミュレートされた Android フレームワーク クラス(アプリ コンテキストなど)を必要とするローカルテストがある場合は、次の手順で AndroidX Test を適切に設定します。
- AndroidX Test のコア依存関係と外部依存関係を追加する
- Robolectric Testing ライブラリの依存関係を追加する
- クラスに AndroidJunit4 テストランナーのアノテーションを追加する
- AndroidX Test コードを記述する
これらの手順を完了して、それがどのように機能しているかを理解します。
ステップ 3: Gradle 依存関係を追加する
- これらの依存関係をアプリ モジュールの
build.gradle
ファイルにコピーして、AndroidX Test のコア依存関係と外部依存関係、Robolectric テスト依存関係を追加します。
app/build.gradle
// AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
ステップ 4. JUnit テストランナーを追加する
- テストクラスの上に
@RunWith(AndroidJUnit4::class)
を追加します。
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
ステップ 5. AndroidX Test を使用する
この時点で、AndroidX Test ライブラリを使用できます。これには、アプリ コンテキストを取得するメソッド ApplicationProvider.getApplicationContex
t
が含まれます。
- AndroidX テスト ライブラリの
ApplicationProvider.getApplicationContext()
を使用してTasksViewModel
を作成します。
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
addNewTask
(tasksViewModel
)に発信します。
TasksViewModelTest.kt
tasksViewModel.addNewTask()
この時点で、テストの内容は以下のようになります。
TasksViewModelTest.kt
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
// TODO test LiveData
}
- テストを実行して動作することを確認します。
コンセプト: AndroidX Test の仕組み
AndroidX Test とは
AndroidX Test は、テスト用のライブラリのコレクションです。このクラスには、Application や Activity などのコンポーネントのバージョン(テスト用)を提供するクラスとメソッドが含まれています。例として、デベロッパーが記述したこのコードは、アプリのコンテキストを取得するための AndroidX Test 関数の例です。
ApplicationProvider.getApplicationContext()
AndroidX Test API の利点の 1 つは、ローカルテストとインストルメンテーション テストの両方で動作するように構築されていることです。これには次のような利点があります。
- ローカルテストまたはインストルメンテーション テストと同じテストを実行できます。
- ローカルテストとインストルメンテーション テストで異なるテスト API について学習する必要はありません。
たとえば、AndroidX Test ライブラリを使用してコードを作成したため、TasksViewModelTest
クラスを test
フォルダから androidTest
フォルダに移動させても、テストは実行されます。getApplicationContext()
の動作は、ローカルテストかインストルメンテーション テストかによって若干異なります。
- インストルメンテーション テストの場合は、エミュレータの起動時または実際のデバイスへの接続時に、実際のアプリのコンテキストを取得します。
- ローカルテストの場合、シミュレートされた Android 環境を使用します。
Robolectric とは
AndroidX Test がローカルテストに使用するシミュレートされた Android 環境は、Robolectric によって提供されています。Robolectric: テスト用に Android 環境をシミュレートするライブラリで、エミュレータの起動やデバイス上での実行よりも高速に実行されます。Robolectric 依存関係を使用しない場合は、次のエラーが発生します。
@RunWith(AndroidJUnit4::class)
の取り組み
テストランナーは、テストを実行する JUnit コンポーネントです。テストランナーがないと、テストは実行されません。JUnit により提供されるデフォルトのテストランナーで、自動的に取得します。@RunWith
は、デフォルトのテストランナーをスワップします。
AndroidJUnit4
テストランナーを使用すると、インストルメンテーション テストかローカルテストかによって、AndroidX Test のテストを個別に行うことができます。
ステップ 6. Robolectric 警告の修正
コードを実行すると、Robolectric が使用されていることに注意してください。
AndroidX Test と AndroidJunit4 のテストランナーのおかげで、Robolectric コードを直接 1 行も書かずに実行できます。
警告が 2 つ表示される場合があります。
No such manifest file: ./AndroidManifest.xml
"WARN: Android SDK 29 requires Java 9..."
Gradle ファイルを更新すると、No such manifest file: ./AndroidManifest.xml
警告を修正できます。
- Gradle ファイルに次の行を追加して、正しい Android マニフェストが使用されるようにします。includeAndroidResources オプションを使用すると、AndroidManifest ファイルを含む単体テストの Android リソースにアクセスできます。
app/build.gradle
// Always show the result of every unit test when running via command line, even if it passes.
testOptions.unitTests {
includeAndroidResources = true
// ...
}
警告 "WARN: Android SDK 29 requires Java 9..."
はさらに複雑です。Android Q でテストを実行するには Java 9 が必要です。Java 9 を使用するように Android Studio を設定する代わりに、この Codelab ではターゲットを SDK を 28 に維持します。
まとめ:
- 純粋なビューモデルのテストは、通常、コードに Android が必要ない
test
ソースセットで行います。 - AndroidX Test ライブラリを使用して、Applications や Activity などのコンポーネントのテスト バージョンを入手できます。
test
ソースセットでシミュレートされた Android コードを実行する必要がある場合は、Robolectric 依存関係と@RunWith(AndroidJUnit4::class)
アノテーションを追加できます。
おめでとうございます。AndroidX テスト ライブラリと Robolectric の両方を使用してテストを実行しています。テストは完了していません(まだアサート ステートメントを記述していませんが、単に「// TODO test LiveData
」と表示されています)。次に、LiveData
を使用してアサート ステートメントを記述する方法を学習します。
このタスクでは、LiveData
値を正しくアサートする方法を学びます。
ここには、addNewTask_setsNewTaskEvent
ビューモデル テストなしで中断した場所が表示されます。
TasksViewModelTest.kt
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
// TODO test LiveData
}
LiveData
をテストするには、次の 2 つの方法をおすすめします。
InstantTaskExecutorRule
を使用するLiveData
の観測を確認する
ステップ 1: InstantTaskExecutorRule を使用する
InstantTaskExecutorRule
は JUnit ルールです。@get:Rule
アノテーションとともに使用すると、テストの前後に InstantTaskExecutorRule
クラスのコードを実行できるようになります(正確なコードを確認するには、キーボード ショートカット Command+B を使用してファイルを表示します)。
このルールにより、アーキテクチャ コンポーネントに関連するすべてのバックグラウンド ジョブが同じスレッドで実行されるため、テスト結果が同期的かつ繰り返し可能な順序で行われるようになります。LiveData のテストを含むテストを作成する場合は、このルールを使用します。
- アーキテクチャ コンポーネントのコアテスト ライブラリ(このルールを含む)の Gradle 依存関係を追加します。
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
TasksViewModelTest.kt
を開きます。TasksViewModelTest
クラス内にInstantTaskExecutorRule
を追加します。
TasksViewModelTest.kt
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Other code...
}
ステップ 2: LiveDataTestUtil.kt クラスを追加する
次のステップは、テスト対象の LiveData
が確実に監視されるようにすることです。
LiveData
を使用する場合、通常はアクティビティまたはフラグメント(LifecycleOwner
)が LiveData
を監視します。
viewModel.resultLiveData.observe(fragment, Observer {
// Observer code here
})
この所見は重要です。LiveData
で以下の操作を行うアクティブなオブザーバーが必要です
onChanged
イベントをトリガーします。- 変換をトリガーする。
ビューモデルの LiveData
に対して想定される LiveData
の動作を取得するには、LifecycleOwner
を指定して LiveData
を監視する必要があります。
これにより、次の問題が発生します。TasksViewModel
テストでは、LiveData
を監視するためのアクティビティやフラグメントがありません。この問題を回避するには、observeForever
メソッドを使用します。このメソッドは、LifecycleOwner
を必要とせずに LiveData
を常に監視できます。observeForever
を行う場合、忘れずにオブザーバーを削除する必要があります。削除しないと、オブザーバーの漏えいのリスクがあります。
コードは次のようになります。次のように確認します。
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// Create observer - no need for it to do anything!
val observer = Observer<Event<Unit>> {}
try {
// Observe the LiveData forever
tasksViewModel.newTaskEvent.observeForever(observer)
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.value
assertThat(value?.getContentIfNotHandled(), (not(nullValue())))
} finally {
// Whatever happens, don't forget to remove the observer!
tasksViewModel.newTaskEvent.removeObserver(observer)
}
}
これは、テストで 1 つの LiveData
をモニタリングするための大量のボイラープレート コードです。このボイラープレートは、いくつかの方法で解消できます。オブザーバーを簡単に追加できるように、LiveDataTestUtil
という拡張関数を作成します。
test
ソースセット内にLiveDataTestUtil.kt
という新しい Kotlin ファイルを作成します。
- 下記のコードをコピーして貼り付けてください。
LiveDataTestUtil.kt
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
@Suppress("UNCHECKED_CAST")
return data as T
}
これはかなり複雑な方法です。オブザーバーを追加して LiveData
値を取得し、オブザーバーをクリーンアップする getOrAwaitValue
という Kotlin 拡張関数を作成します。基本的には、上記の observeForever
コードの再利用可能な短いバージョンです。このクラスの詳細については、こちらのブログ投稿をご覧ください。
ステップ 3: getOrAwaitValue を使用してアサーションを書き込む
このステップでは、getOrAwaitValue
メソッドを使用して、newTaskEvent
がトリガーされたことを確認するアサート ステートメントを記述します。
getOrAwaitValue
を使用してnewTaskEvent
のLiveData
値を取得します。
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- 値が null ではないことのアサーションを行います。
assertThat(value.getContentIfNotHandled(), (not(nullValue())))
完全なテストは次のコードのようになります。
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
assertThat(value.getContentIfNotHandled(), not(nullValue()))
}
}
- コードを実行し、テスト合格を観察しましょう。
テストの作成方法を確認しました。次は、テストを自分で作成します。このステップでは、習得したスキルを使って、別の TasksViewModel
テストの作成を練習します。
ステップ 1: 独自の ViewModel テストを作成する
「setFilterAllTasks_tasksAddViewVisible()
」と記述します。テストでは、フィルタの種類ですべてのタスクを表示するように設定している場合に、[タスクを追加] ボタンが表示されていることを確認します。
- 参照用に
addNewTask_setsNewTaskEvent()
を使用して、フィルタモードをALL_TASKS
に設定し、tasksAddViewVisible
LiveData がtrue
であることのアサーションを行う、setFilterAllTasks_tasksAddViewVisible()
というテストをTasksViewModelTest
に記述します。
まずは、以下のコードをご利用ください。
TasksViewModelTest
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
// When the filter type is ALL_TASKS
// Then the "Add task" action is visible
}
注:
- すべてのタスクの
TasksFilterType
列挙型はALL_TASKS.
です。 - タスクを追加するボタンを表示するかどうかは、
LiveData
tasksAddViewVisible.
で制御します
- テストを実行します。
ステップ 2: テストとソリューションを比較する
このソリューションと下記のソリューションを比較してください。
TasksViewModelTest
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
}
次の点を確認します。
- 同じ AndroidX
ApplicationProvider.getApplicationContext()
ステートメントを使用してtasksViewModel
を作成します。 setFiltering
メソッドを呼び出して、ALL_TASKS
フィルタタイプの列挙型を渡します。getOrAwaitNextValue
メソッドを使用して、tasksAddViewVisible
が true であることを確認します。
ステップ 3: @Before ルールを追加する
両方のテストの開始時に、TasksViewModel
を定義することに注意してください。
TasksViewModelTest
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
複数のテストでセットアップ コードを繰り返す場合は、@Before アノテーションを使用してセットアップ メソッドを作成し、繰り返したコードを削除できます。これらのテストはすべて TasksViewModel
をテストするので、ビューモデルが必要です。このコードを @Before
ブロックに移動します。
tasksViewModel|
というlateinit
インスタンス変数を作成します。setupViewModel
というメソッドを作成します。@Before
アノテーションを付けます。- ビューモデルのインスタンス化コードを
setupViewModel
に移動します。
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- コードを実行します。
警告
次のことは行わないでください。
tasksViewModel
次のように定義します。
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
これにより、すべてのテストに同じインスタンスが使用されます。各テストでは、テスト対象(この場合は ViewModel)の新しいインスタンスを使用する必要があるため、これは避ける必要があります。
TasksViewModelTest
の最終コードは以下のようになります。
TasksViewModelTest
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
@Test
fun addNewTask_setsNewTaskEvent() {
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.awaitNextValue()
assertThat(
value?.getContentIfNotHandled(), (not(nullValue()))
)
}
@Test
fun getTasksAddViewVisible() {
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
}
}
開始したコードと最終的なコードの差分を確認するには、こちらをクリックしてください。
完成した Codelab のコードをダウンロードするには、次の git コマンドを使用します。
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
リポジトリを ZIP ファイルとしてダウンロードして解凍し、Android Studio で開くこともできます。
この Codelab では以下を行いました。
- Android Studio からテストを実行する方法。
- ローカルテスト(
test
)とインストルメンテーション テスト(androidTest
)の違い。 - JUnit と Hamcrest を使用してローカル単体テストを作成する
- AndroidX Test Library を使用して ViewModel テストを設定する
Udacity コース:
Android デベロッパー ドキュメント:
- アプリ アーキテクチャ ガイド
- JUnit4
- ハムレスト
- Robolectric Testing ライブラリ
- AndroidX テスト ライブラリ
- AndroidX アーキテクチャ コンポーネントのコアテスト ライブラリ
- ソースセット
- コマンドラインからテストする
動画:
その他:
このコースの他の Codelab へのリンクについては、Kotlin Codelab の高度な Codelab のランディング ページをご覧ください。