この Codelab は、Kotlin を使った高度な Android 開発コースの一部です。Codelab を順番に進めると、このコースを最大限に活用できますが、これは必須ではありません。コースの Codelab はすべて、Kotlin を使った高度な Android 開発の Codelab ランディング ページに記載されています。
はじめに
最初のアプリの最初の機能を実装したときに、コードを実行して想定どおりに動作することを確認したでしょう。テスト(手動テスト)を実施しました。機能を追加、更新するたびに、コードを実行して動作を確認していたことでしょう。ただし、これを毎回手動で行うのは面倒で、ミスが発生しやすく、スケーリングできません。
コンピュータはスケーリングと自動化に優れています。そのため、大企業から中小企業まで、さまざまな企業のデベロッパーが自動テストを記述します。これは、ソフトウェアによって実行されるテストであり、コードが動作することを確認するためにアプリを手動で操作する必要はありません。
この一連の Codelab では、実際のアプリのテストのコレクション(テストスイート)を作成する方法を学びます。
この最初の Codelab では、Android でのテストの基本について説明します。最初のテストを作成し、LiveData
と ViewModel
をテストする方法を学びます。
前提となる知識
以下について把握しておく必要があります。
- Kotlin プログラミング言語
- 次のコア Android Jetpack ライブラリ:
ViewModel
とLiveData
- アプリ アーキテクチャ ガイドと Android の基礎 Codelab のパターンに準拠したアプリケーション アーキテクチャ
学習内容
このコースでは、次のトピックについて学習します。
- Android で単体テストを作成して実行する方法
- テスト駆動開発の使用方法
- インストルメンテーション テストとローカルテストの選択方法
次のライブラリとコードのコンセプトについて説明します。
演習内容
- Android でローカルテストとインストルメンテーション テストの両方をセットアップ、実行、解釈する。
- JUnit4 と Hamcrest を使用して Android で単体テストを作成します。
- 簡単な
LiveData
テストとViewModel
テストを作成します。
この一連の Codelab では、TO-DO Notes アプリを使用します。このアプリでは、完了するタスクを書き留めてリストに表示できます。完了または未完了としてマークしたり、フィルタしたり、削除したりできます。
このアプリは Kotlin で記述されており、複数の画面があり、Jetpack コンポーネントを使用し、アプリ アーキテクチャ ガイドのアーキテクチャに沿って実行されます。このアプリのテスト方法を学ぶことで、同じライブラリとアーキテクチャを使用するアプリをテストできるようになります。
まず、コードをダウンロードします。
または、コードの GitHub リポジトリのクローンを作成することもできます。
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
このタスクでは、アプリを実行してコードベースを確認します。
ステップ 1: サンプルアプリを実行する
TO-DO アプリをダウンロードしたら、Android Studio で開いて実行します。コンパイルされるはずです。次の手順に沿って、アプリを確認します。
- フローティング アクション ボタンのプラスアイコンを使用して、新しいタスクを作成します。まずタイトルを入力し、次にタスクに関する追加情報を入力します。緑色のチェックマークの FAB で保存します。
- タスクのリストで、完了したタスクのタイトルをクリックし、そのタスクの詳細画面で残りの説明を確認します。
- リストまたは詳細画面で、タスクのチェックボックスをオンにして、ステータスを [完了] に設定します。
- タスク画面に戻り、フィルタ メニューを開いて、[Active](有効)と [Completed](完了)のステータスでタスクをフィルタします。
- ナビゲーション ドロワーを開き、[統計情報] をクリックします。
- 概要画面に戻り、ナビゲーション ドロワー メニューから [完了済みをクリア] を選択して、ステータスが [完了] のタスクをすべて削除します。
ステップ 2: サンプルアプリのコードを確認する
TO-DO アプリは、一般的な Architecture Blueprints テストとアーキテクチャのサンプル(サンプルのリアクティブ アーキテクチャ バージョンを使用)に基づいています。アプリは、アプリ アーキテクチャ ガイドのアーキテクチャに沿って実行されます。フラグメント、リポジトリ、Room で ViewModel を使用します。以下の例のいずれかをご存知であれば、このアプリのアーキテクチャも同様です。
- Room とビュー Codelab
- Android Kotlin の基礎トレーニングのコードラボ
- 高度な Android トレーニングの Codelab
- Android Sunflower サンプル
- Kotlin による Android アプリの開発 Udacity トレーニング コース
1 つのレイヤのロジックを深く理解するよりも、アプリの一般的なアーキテクチャを理解する方が重要です。
パッケージの概要は次のとおりです。
パッケージ: | |
| タスクの追加または編集画面: タスクの追加または編集用の UI レイヤコード。 |
| データレイヤー: タスクのデータレイヤーを処理します。データベース、ネットワーク、リポジトリのコードが含まれています。 |
| 統計情報画面: 統計情報画面の UI レイヤコード。 |
| タスクの詳細画面: 単一のタスクの UI レイヤコード。 |
| タスク画面: すべてのタスクのリストの UI レイヤコード。 |
| ユーティリティ クラス: アプリのさまざまな部分で使用される共有クラス(複数の画面で使用されるスワイプ更新レイアウトなど)。 |
データレイヤ(.data)
このアプリには、remote パッケージのシミュレートされたネットワーキング レイヤと、local パッケージのデータベース レイヤが含まれています。このプロジェクトでは、簡略化のため、実際のネットワーク リクエストを行うのではなく、遅延のある 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: ローカルテストを実行する
test
フォルダを開き、ExampleUnitTest.kt ファイルを見つけます。- 右クリックして [Run ExampleUnitTest] を選択します。
画面下部の [実行] ウィンドウに、次の出力が表示されます。
- 緑色のチェックマークが表示されていることに注目し、テスト結果を開いて、
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 つのテスト)。- 通常、アサーション ステートメントが含まれます。
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] > [Test] を選択します。
[テストを作成] ダイアログが開きます。
- クラス名: を
StatisticsUtilsTest
に変更します(StatisticsUtilsKtTest
ではなく。テストクラス名に KT が含まれない方が少し見やすくなります)。 - その他のデフォルトはそのままにします。JUnit 4 が適切なテスト ライブラリです。宛先パッケージが正しい(
StatisticsUtils
クラスの場所を反映している)ことを確認し、チェックボックスをオンにする必要はありません(チェックボックスをオンにすると追加のコードが生成されますが、テストは最初から作成します)。 - [OK] を押します。
[Choose Destination Directory] ダイアログが開きます。
関数は数学計算を行うもので、Android 固有のコードは含まれないため、ローカル テストを行います。そのため、実機またはエミュレートされたデバイスで実行する必要はありません。
- ローカルテストを作成するため、
test
ディレクトリ(androidTest
ではない)を選択します。 - [OK] をクリックします。
test/statistics/
にStatisticsUtilsTest
クラスが生成されていることに注意してください。
ステップ 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
を右クリックして [実行] を選択します)。
合格する必要があります。
ステップ 3: Hamcrest の依存関係を追加する
テストはコードの動作を説明するドキュメントの役割も果たすため、人間が読めるようにすると便利です。次の 2 つのアサーションを比較します。
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
2 つ目のアサーションは、人間が書いた文にずっと近いものになっています。これは、Hamcrest というアサーション フレームワークを使用して記述されています。読みやすいアサーションを作成するのに役立つツールとして、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 では、Hamcrest のすべての詳細を説明しません。詳細については、公式チュートリアルをご覧ください。
これは練習用のオプションのタスクです。
このタスクでは、JUnit と Hamcrest を使用してテストを作成します。また、テスト駆動開発のプログラム プラクティスから派生した戦略を使用してテストを作成します。テスト駆動開発(TDD)は、機能コードを最初に記述するのではなく、テストを最初に記述するというプログラミングの考え方です。次に、テストに合格することを目標に、機能コードを記述します。
ステップ 1. テストを作成する
通常のタスクリストがある場合のテストを作成します。
- 完了したタスクが 1 つあり、アクティブなタスクがない場合、
activeTasks
の割合は0f
、完了したタスクの割合は100f
になります。 - 完了したタスクが 2 つ、アクティブなタスクが 3 つある場合、完了率は
40f
、アクティブ率は60f
になります。
ステップ 2. バグのテストを作成する
記述されている getActiveAndCompletedStats
のコードにはバグがあります。リストが空または null の場合に何が起こるかを適切に処理していないことに注意してください。どちらの場合も、両方の割合が 0 になるはずです。
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()
)
}
コードを修正してテストを作成するには、テスト駆動開発を使用します。テスト駆動開発は次の手順で行われます。
- Given、When、Then の構造を使用して、命名規則に沿った名前でテストを記述します。
- テストが失敗することを確認します。
- テストに合格するための最小限のコードを記述します。
- すべてのテストで繰り返します。
バグの修正から始めるのではなく、まずテストの作成から始めます。これにより、今後これらのバグが誤って再導入されるのを防ぐテストがあることを確認できます。
- 空のリスト(
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] を選択します。
- [テストの作成] 画面で、[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
のインスタンスを作成する場合、そのコンストラクタには Application Context が必要です。ただし、このテストではアクティビティ、UI、フラグメントを含む完全なアプリケーションを作成しないため、アプリケーション コンテキストをどのように取得すればよいでしょうか?
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(???)
AndroidX テスト ライブラリには、テスト用の Application や Activity などのコンポーネントのバージョンを提供するクラスとメソッドが含まれています。ローカルテストでシミュレートされた Android フレームワーク クラス(Application Context など)が必要な場合は、次の手順に沿って AndroidX Test を適切に設定します。
- AndroidX Test の core と ext の依存関係を追加する
- Robolectric テスト ライブラリの依存関係を追加する
- クラスに AndroidJunit4 テストランナーのアノテーションを付ける
- AndroidX Test コードを作成する
これらの手順を完了し、その後、それらの手順が連携して何を行うかを理解します。
ステップ 3. Gradle 依存関係を追加する
- これらの依存関係をアプリ モジュールの
build.gradle
ファイルにコピーして、AndroidX Test Core と ext の依存関係、および 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 ライブラリを使用できます。これには、Application Context を取得する ApplicationProvider.getApplicationContex
t
メソッドが含まれます。
- AndroidX テスト ライブラリの
ApplicationProvider.getApplicationContext()
を使用してTasksViewModel
を作成します。
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
tasksViewModel
のaddNewTask
さんに電話します。
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 は、テスト用のライブラリのコレクションです。テスト用のアプリケーションやアクティビティなどのコンポーネントのバージョンを提供するクラスとメソッドが含まれています。たとえば、このコードは、アプリケーション コンテキストを取得するための AndroidX Test 関数の例です。
ApplicationProvider.getApplicationContext()
AndroidX Test API のメリットの 1 つは、ローカルテストとインストゥルメント化テストの両方で動作するように構築されていることです。この方法のメリットは次のとおりです。
- 同じテストをローカルテストまたはインストルメンテーション テストとして実行できます。
- ローカルテストとインストルメンテーション テストで異なるテスト API を学習する必要はありません。
たとえば、AndroidX Test ライブラリを使用してコードを記述したため、TasksViewModelTest
クラスを test
フォルダから androidTest
フォルダに移動しても、テストは引き続き実行されます。getApplicationContext()
の動作は、ローカルテストとして実行されるか、インストルメンテーション テストとして実行されるかによって若干異なります。
- インストルメンテーション テストの場合、エミュレータの起動時または実機への接続時に提供される実際の Application コンテキストを取得します。
- ローカルテストの場合は、シミュレートされた 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
警告を修正できます。
- 正しい Android マニフェストが使用されるように、gradle ファイルに次の行を追加します。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 が必要です。この Codelab では、Java 9 を使用するように Android Studio を構成するのではなく、ターゲット SDK とコンパイル SDK を 28 に設定します。
まとめると、次のとおりです。
- 純粋なビューモデル テストは、通常
test
ソースセットに配置できます。コードで Android が必要になることは通常ないためです。 - AndroidX テスト ライブラリを使用すると、アプリケーションやアクティビティなどのコンポーネントのテスト バージョンを取得できます。
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 Rule です。@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
を監視するアクティビティやフラグメントがありません。この問題を回避するには、LifecycleOwner
を必要とせずに LiveData
を常に監視する observeForever
メソッドを使用します。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
であることをアサートするTasksViewModelTest
のsetFilterAllTasks_tasksAddViewVisible()
というテストを記述します。
以下のコードを使用して開始します。
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))
}
以下の操作を行っているかどうかを確認します。
tasksViewModel
は、同じ AndroidXApplicationProvider.getApplicationContext()
ステートメントを使用して作成します。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 ライブラリを使用して ViewModel テストを設定する。
Udacity コース:
Android デベロッパー ドキュメント:
- アプリ アーキテクチャ ガイド
- JUnit4
- Hamcrest
- Robolectric テスト ライブラリ
- AndroidX テスト ライブラリ
- AndroidX アーキテクチャ コンポーネント コアテスト ライブラリ
- ソースセット
- コマンドラインからテストする
動画:
その他:
このコースの他の Codelab へのリンクについては、Kotlin を使った高度な Android 開発の Codelab のランディング ページをご覧ください。