LoginSignup
3
2
iOS強化月間 - iOSアプリ開発の知見を共有しよう -

Konsist を使って KMP ライブラリモジュールの作業抜けを単体テストで検出する

Last updated at Posted at 2023-09-10

経緯

ある日 AndroiDagashi を見ていたら #290 2023-08-27

アーキテクチャをテストできる新ツール、 Konsist

というものが紹介されていて、その GitHub のトップページを眺めてみたら、所属先の Kotlin Multiplatform (KMP) を用いたアプリ開発でよくある作業抜けを CI で検出できそうだと思ったので、まずは自分の学習用サンプルコードで試してみました。

Konsist の紹介として、実装クラスに対する internal の設定抜けを単体テストで検出する

Konsist の GitHub トップページには複数の使用例があり、それを読むだけで大まかな使い方を理解できると思いますが、ここでは internal の設定抜けを単体テストで検出するための Konsist の書き方を紹介します。

あるアプリではマルチモジュールを導入していて、データの処理をモジュールにまとめています。

参考 一般的なモジュール化パターン - データモジュール

データの処理を代表するクラスを ○○RepositoryImpl 、そのインターフェースを ○○Repository として、インスタンスはインターフェースを指定して Hilt などの DIコンテナから取得する形を取りました。
そうなると、実装クラスである ○○RepositoryImpl には internal が付いていることが望ましいです。すべての ○○RepositoryImpl クラスに internal が付いているかを Konsist を使い単体テストで確認します。

ArchitectureTest.kt
class ArchitectureTest {
    @Test
    fun checkAllRepositoryImplsAreInternal() {
        Konsist.scopeFromProject() // このプロジェクトの
            .classes() // すべてのクラスリストを取得。型は List<KoClassDeclaration>
            .withNameEndingWith("RepositoryImpl") // 名前が RepositoryImpl で終わる要素に限定する
            .assertTrue { it.hasInternalModifier } // すべての要素に対して internal が付いているか確認する
    }
}

KMP モジュールのテストから Konsist を使えるようにする

Konsist は JVM ターゲットからしか使えないので、それを追加して jvmTest の依存ライブラリとして追加します。

libs.versions.toml
konsist = 'com.lemonappdev:konsist:0.13.0'
build.gradle.kts
kotlin {
    sourceSets {
        // 略
        jvm()
        jvmTest.dependencies {
            implementation(kotlin("test"))
            implementation(libs.konsist)
        }
    }
}

commonTest 方ではテストクラスから Konsist が使えません。Konsist は内部で kotlin-stdlib-jdk8 を使っていて、それは JVM に依存するからです。

Koin の iOS 向け DI 設定の書き忘れを単体テストで検出する

私の学習用サンプルコードでは DI コンテナに Koin を使っています。Koin で作成されたインスタンスを iOS から使うためには、次のリンク先にあるようにヘルパークラスの実装が必要です。

Kotlin Multiplatform Dependency Injection - Injected Classes

ここで iOS/Android 両方から使う状態ホルダーのクラスがあったとして、それに対応するヘルパクラスの実装を忘れたとします。ひとつのリポジトリを私1人で作る分には iOS 側の UI を作るついでに作れば良いです。しかし KMP と iOS の GitHub リポジトリが別で開発担当者も別の場合、iOS の開発担当者がすぐにその状態ホルターを使い UI を作ることができません。

そこで状態ホルダーのクラスが作られた時点で、対応するヘルパクラスが無ければ単体テストを失敗にして CI を失敗にするようにします。

私の学習用サンプルコードは Redux アーキテクチャを採用していて、状態ホルダーは ActionCreator と Reducer で構成されています。その2つのインスタンスは次のようなコードで、iOS から使えるようにしています。

HomeViewModelHelper.kt
class HomeViewModelHelper : IosViewModelHelperBase<HomeEvent, HomeAction, HomeState, HomeEffect>() {
    // 親の abstract class に Redux に従い iOS 側の UI 状態を更新するための処理が入っている

    override val actionCreator: HomeActionCreator by inject()

    override val reducer: HomeReducer by inject()
}

Konsist を使い ○○ActionCreator/○○Reducer と対になる ○○ViewModelHelper が作られているかをチェックする単体テストを書きます。設置場所は commonTest ではなく jvmTest になります。

ArchitectureTest.kt
class ArchitectureTest {
    @Test
    fun checkAllActionCreatorsAndReducersAreRegisteredInViewModelHelper() {
        // 名前が ViewModelHelper で終わるクラスのプロパティの型の名前 Set を作成する。
        val registeredClassNames = Konsist.scopeFromProject()
            .classes()
            .withNameEndingWith("ViewModelHelper")
            .flatMap {
                it.properties()
            }.map {
                it.type?.name ?: ""
            }.toSet()
        // 名前が ActionCreator で終わるクラスと Reducer で終わるクラスの名前 Set を作成する。
        val actionCreators = Konsist.scopeFromProject()
            .classes()
            .withNameEndingWith("ActionCreator")
        val reducers = Konsist.scopeFromProject()
            .classes()
            .withNameEndingWith("Reducer")
        val checkClassNames = (actionCreators + reducers).map {
            it.name
        }.toSet()
        // 両者が同じかを確認する
        Assert.assertEquals(checkClassNames, registeredClassNames)
    }
}

まとめ

単体テストで Konsist を使うことで、Kotlin コードのクラス名や修飾子、プロパティなどが、ガイドラインに沿っているかをチェックすることができます。また KMP においては JVM ターゲットのテストとして作成します。

チーム開発において完璧と思って作成したプルリクが Approved になったのでマージしたところ、あとで作業抜けに気がついて追加でプルリクを作成することがありがちな場合は、Konsist を使った単体テストによるチェック項目にすることで、開発効率を向上できると思いました。

3
2
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
3
2