0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

これから始めるKotestの導入〜書き方

Posted at

はじめに

まだAndroidエンジニアとして働き始めてから2週間目の新参者ですが、ユニットテストを書いたので忘れないようにここに書いておきます。

導入

自分はすでに導入されているプロジェクトでやったので、導入していませんが以下の記事のようにbuild.gradleに依存関係を書いたりするだけでサクッと導入できるようです!

参考記事:

テストの仕方

1. テストクラスの作成

実装したいクラスの適当な関数にフォーカスを当てて右クリックし
Generate -> Testを選択。
するとTesting Libraryやクラスの名前を入力するウィンドウが出てきます。
Testing LibraryはKoTestをインストール済みであればそれで、その他は各個人であったものを選ぶと良いと思います。
自分は作成時にKotestプラグインを入れていなかったのでJUnit4にしました。
そのほか継承したいテストクラスやBeforeなど最初から必要なければ「OK」を押してクラスを作成してください。

すると自動でtestパッケージ配下にプロダクションコードがあるのと同じディレクトリ構成で該当のクラスが作成されます!

2. テスト前の下準備

他の言語のテストと同様にテストの実行前後に共通で行いたい処理を書いたり、該当クラスに必要な依存関係のあるクラスのモック化などを行う必要があります。
初めから全体のテストクラス内の構成を書いてしまいますと、


@OptIn(ExperimentalCoroutinesApi::class) // お作法的な書き方
class HogeViewModelTest : FunSpec({ // FunSpecに関してもSpecというお決まりの書き方
    val testDispatcher = StandardTestDispatcher() // コルーチンをテストしやすくするための機能
    lateinit var mockHogeRepository: HogeRepository // クラスの初期化時に必要な依存クラスなどのmock
    lateinit var viewModel: HogeViewModel // テストするクラス(今回はViewModel)

    /**
     * 共通で行いたい処理がある場合はメソッド化しても良い
     * 
     */
    fun executeCommonMethod(
        piyo: PiyoInfo,
        testScheduler: TestCoroutineScheduler
    ) {
       // 共通処理
    }

    // テストクラスのライフサイクル

   /**
     * 各テストケースの実行前に毎回実行"
     * 
     */
    beforeSpec {
        // コルーチンを利用しているコードで必須の設定
        // メインスレッドのディスパッチャーをテスト用に置き換え
        Dispatchers.setMain(testDispatcher)
    }
    
   /**
     * "テストクラス全体の実行後に1回だけ実行
     * 
     */
    afterSpec {
        // テスト用のディスパッチャーの設定をリセット
        Dispatchers.resetMain()
    }

   /**
     * 各テストケースの実行前に毎回実行
     * 
     */
    beforeEach {
        // 基本的にモックのセットアップなどをおこなう
        mockHogeRepository = mockk<HogeRepository>()
        viewModel = HogeViewModel(mockHogeRepository)

        // 静的メソッドをモック化
        // ロガーなどstaticメソッドなどを利用している場合
        mockkStatic(Logger::class)
        every { Logger.send(any()) } just runs
    }

   /**
     * 各テストケースの実行後に毎回実行
     * 
     */
    afterEach {
        // 設定したモックなどをクリアする。
        clearAllMocks()
        unmockkStatic(Logger::class)
    }


    // --------------以下テストコード--------------
    context("Hogeクラステスト") {
        test("正常系") {
                runTest {
                  // 具体的なテストの記載
                   // 基本的にはshouldBeなどを利用して判定を行う。
                   hogeMethod()
                   viewModel.uiState.first() shouldBe HogeViewModel.UiState.Success
                }
        }
    }
})

といった感じになります。

テスト実施前後で必要な処理のライフサイクルは

  • beforeSpec
  • afterSpec
  • beforeEach
  • afterEach

の4つです。
呼ばれるタイミングは

➀ beforeSpec
    ➂ beforeEach
        テストケース1
    ➃ afterEach
    ➂ beforeEach
        テストケース2
    ➃ afterEach
➁ afterSpec

です。

アサーションは他の言語のユニットテストのように等価検証だけでなくNullではないかなど色々なレパートリーがあります。
今回上記サンプルコードで載せているsholdBeはJUnitのassertEqualsに相当します。
もう少し具体的に書くと、viewModelなので、MutableStateFlowを持っている前提で、hogeMethod()を行った際にuiStateが想定の状態になっているかという確認になります。


// 例クラス)
@HiltViewModel
class HogeViewModel @Inject constructor(
    private val hogeRepository: HogeRepository,
) : ViewModel() {

    〜〜省略〜〜

    sealed class UiState {
    object Initial : UiState()
    data class Success : UiState()
    data class Error(val message: String) : UiState()
    }

    fun hogeMethod() {
        _uiState.value = UiState.Success
    }
}

最後に

ユニットテストを始めてみたいけどいまいち何が必要で、それぞれなんの処理をしているのかわからない自分のようなペーペーの方向けです。
自分も書きながら整理できたところがあるので、引き続き書くのを癖にやっていければと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?