2024年7月6日土曜日

Android テストコードの実装について解説

どうも。どっことです。今回は、テストコードの実装について解説したいと思います。

テストコード

テストコードは実機やエミュレータに開発段階のアプリをインストールせずとも、プログラムとしてテストを実行できるようにするものです。テストコードを実装せずとも、世の中に公開するアプリを開発することができるので、より難しい機能の実装や複雑なドメインロジックをプログラムに落とし込むなどのスキルを優先して獲得するというロードマップも考えとしてはあると思います。

ただ、昨今のCI/CDの重要性の高まりを鑑みると、テストコードを実装できることもエンジニアとして必要なスキルとして需要が上がってきているように見受けられます。そしてそんな需要が高まっている一方で、テストコードを実装できるエンジニアはまだまだ多くありません。ぜひテストコードを実装できるようになり、より広い知見をもつエンジニアへと一歩を進めましょう。

説明に入る前に(テストコードの種類)

Androidの開発においては、大きく2種類のテストコードがあります。

ユニットテスト

開発PC上で実行されるテストコードです。Androidの機能に依存しないビジネスロジックやアプリケーションロジックに対するテストコードを実装する際のものとなります。開発PC上で実行されるため、後述のインストルメントテストよりも高速に実行・実施ができます。

インストルメントテスト

実機やエミュレーター上で実行されるテストコードです。こちらは、Androidの機能に依存するUIまわりのテストや、外部機能などとの連携に対するテストコードとなります。より現実に近い環境でのテストとなるため、ユーザーストーリーを意識したテストに向いています。一方で、インストールなどのステップがビルド時に含まれるため、実行時間が比較的遅めになります。

今回は、ユニットテストの実装方法について解説します。

ユニットテストを実装する

ユニットテストの実装方法について、以下の流れで説明します。

  • ライブラリの準備
  • 実装
  • その他
    • Robolecticの紹介

ライブラリの準備

ユニットテストのテストコードを実装するためには以下をbuild.gradleに追加が必要です。プロジェクトを新規作成する際にAndroid Studioが自動で追加している場合がありますので、追加が必要か確認してください。

dependencies {
    testImplementation("junit:junit:4.13.2")
}

実装

それでは実装していきましょう。

テスト対象

テスト対象は以下のクラス・メソッドとします。

class Sample {
   fun test(input: Int?): Boolean {
       if (input == null) {
           throw IllegalArgumentException()
       }
       return input > 0
    }
}

テスト観点

テスト対象となるメソッドは、以下の観点でテストコードを実装することができます。

  • trueが返される確認テスト
  • falseが返される確認テスト
  • 例外が投げられるテスト

上をベースに確認するテストコードを追加していきましょう。

テストクラスを追加する

テストを実装するため、テストクラスを実装します。テスト対象のクラスで option + Enter からCreate Testを選択すればテストクラスを作成できます。SampleTestという名前にすると、testディレクトリに以下のようなクラスが作成できると思います。

class SampleTest {
}

trueが返される確認テストを実装する

まずはtrueが返されることのテストです。

@Test
fun testTrue() {
    val sample = Sample()
    val value = sample.test(1)
    Assert.assertEquals(value, true)
}

以降の実装も同様ですが、テストメソッドを実装し@Testアノテーションを付与します。

そして、実際にテストしたい内容を実装します。今回は、入力値に1以上を指定した場合はtrueが返却されるので、それを確認するためのテストを実装します。

falseが返される確認テストを実装する

次にfalseが返されることのテストです。

@Test
fun testFalse() {
    val sample = Sample()
    val value = sample.test(0)
    Assert.assertEquals(value, false)
}

同様に、falseが返却されるケースを実装します。入力値に0以下を指定した場合はfalseが返却されるので、それを確認するためのテストを実装します。

例外が投げられるテストを実装する

そして、例外が投げられることのテストです。

@Test(expected = IllegalArgumentException::class)
fun testException() {
    val sample = Sample()
    sample.test(null)
    Assert.fail()
}

例外が発生するテストは@Testアノテーションのパラメータexpectedを指定します。そのパラメータには発生する例外のクラスを指定します。

また、例外が発生しなかった場合がテストがNGとなるように、例外がthrowされる直後のタイミングでAssert.fail()を実装しておきます。

これにて、該当クラスメソッドのテストが実装できました。あとはテストを実行すれば期待した結果になるかを確認する処理が実行されます。

その他

ここまでテストコードの実装について解説しましたが、実は1点問題があります。ユニットテストは開発PC上で実行されますが、その都合により、AndroidSDKに含まれる機能を利用することができません。これが結構ネックになり、例えばよくある実装の一つであるFragmentのインスタンス生成メソッドは、Fragmentやそれに渡すBundleがAndroidSDKに含まれるものなので、テストコードを実装することができません。

ではどうするかというと、一つはインストルメントテストで実装することです。実機やエミュレーター上でテストすることで、テストコードを実行することができます。

もう一つはRobolectricを利用することです。RobolectricはユニットテストでもAndroidSDKに含まれる機能を利用することができます。

Robolectricの紹介

今回は説明を省略しますが、公式サイトを紹介します。またRobolectricの利用方法についても、後日解説を予定しています。

まとめ

今回はテストコード、特にユニットテストの実装方法について解説しました。テストコードやその実装の重要性は今後も高いものと予想されるので、今後もこれをテーマとした投稿をしていこうと考えていますので、よろしくお願いします。

参考

移行予定

どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。