mcpField
W
writing-tests-with-kotlin-test

writing-tests-with-kotlin-test

github.com/skydovesテスト・品質33961 回インストール22 週間前に更新
リポジトリ

writing-tests-with-kotlin-test について

Kotlin のテスト作成者向けに、kotlin.test のアサーション、アノテーション、Gradle 設定を解説するスキルです。JVM、Android、JS、Kotlin Multiplatform で共有できる、テストフレームワーク非依存の API を扱います。

主な機能

  1. 01ターゲット共通のアサーションとアノテーション
  2. 02例外・型アサーションのパターン
  3. 03@OnlyInputTypes によるコンパイル時チェック
  4. 04Asserter 拡張ポイントの解説
  5. 05Gradle 依存関係の設定

活用シーン

  1. 01commonTest で共有テストを作成
  2. 02複数ターゲットでテストソースを再利用
  3. 03アサーションの選択と例外の検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/skydoves/android-testing-skills --skill writing-tests-with-kotlin-test

インストール後、Claude Code で/writing-tests-with-kotlin-testを入力して読み込まれたか確認します。

詳細

kotlin.test を使ったテストの作成 — すべてのターゲットで共通のアサーション API

kotlin.test は Kotlin 独自の軽量なテストライブラリです。フレームワークに依存しない assert* 関数と、クラスパス上にあるテストランナー(JUnit4、JUnit5、TestNG、JS/Native のランナー)に型エイリアスで対応する @Test/@BeforeTest/@AfterTest アノテーションを提供します。一度 import kotlin.test.* と記述すれば、同じテストソースが commonTest、JVM、すべての Kotlin Multiplatform ターゲットでコンパイルできます。このスキルでは、API の概要、@OnlyInputTypes によるコンパイル時のガード、Asserter の拡張ポイント、Gradle の設定方法を扱います。これはアサーション層です。Android のインストルメンテーションにおける ランナー 層については、../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md を参照してください。

このスキルを使う場面

  • モジュールが Kotlin Multiplatform で、テストが commonTest にある場合。JUnit の org.junit.* は利用できませんが、kotlin.test は利用できます。
  • JVM、Android、JS、Native で同じように読めるアサーションの語彙を使いたい場合。
  • ユーザーが assertFailsWith<IllegalStateException> { … }、assertContentEquals(...)、assertIs<Foo>(value)、assertContains(list, x)、expect(3) { compute() }、または @BeforeTest/@AfterTest を使おうとしている場合。
  • JVM モジュールに testImplementation(kotlin("test")) があり、「JUnit を追加した覚えがないのにテストが JUnit で実行される」とユーザーが疑問に思っている場合 — 機能ベースの依存関係解決について説明します。
  • kotlin.test.assertEquals の失敗が差分のない単純な AssertionError として表示され、ユーザーが JUnit 形式の比較エラーに戻したい場合。

このスキルを使わない場面

  • ユーザーが流れるような豊富なアサーション(assertThat(x).isEqualTo(...)、ソフトアサーション、コレクションのマッチャー)を求めている場合 — それは AssertJ/Google Truth/Kotest のアサーションであり、意図的に kotlin.test の最小限の範囲から外れています。
  • ユーザーが Android のインストルメンテーションテストを書いていて、ランナー(@RunWith(AndroidJUnit4::class)、AndroidJUnitRunner、androidx.test:*)について尋ねている場合 — ../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md と ../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md を使ってください。(これらのテスト内で kotlin.test のアサーションを使うことはできます。)
  • ユーザーがコルーチン/Flow をテストしている場合 — runTest、TestScope、Dispatchers.setMain については ../../../jvm-tests/coroutines/testing-coroutines-with-runtest/SKILL.md、Turbine については ../../../jvm-tests/coroutines/testing-flows-with-turbine/SKILL.md を参照してください。(その中での assert* 呼び出しには kotlin.test を使えます。)
  • ユーザーが Compose UI のアサーション(assertIsDisplayed、assertTextEquals)を求めている場合 — それは compose/assertions/ のスキルであり、kotlin.test とは無関係です。

前提条件

  • Gradle:JVM/Android モジュールでは testImplementation(kotlin("test")) を使用します。Kotlin Multiplatform モジュールでは、kotlin.test を commonTest ソースセットに追加します:sourceSets { commonTest.dependencies { implementation(kotlin("test")) } }。
  • kotlin("test") の省略記法は、機能ベースの依存関係です(Kotlin 1.4 以降)。JVM ではデフォルトで JUnit4 バリアント(kotlin-test-junit)に解決されます。Test タスクが useJUnitPlatform() で設定されている場合は、代わりに JUnit5 バリアント(kotlin-test-junit5)に解決されます。特定のアダプターを明示的に依存関係として指定することもできます:org.jetbrains.kotlin:kotlin-test-junit、:kotlin-test-junit5、:kotlin-test-testng、:kotlin-test-js。
  • 選択したターゲットに対応するテストランナーがクラスパス上に必要です。kotlin.test の @Test はアノテーションにすぎず、テストを実行するもの(JUnit、TestNG、Kotlin/JS または Kotlin/Native のテスト基盤)が必要です。アダプターのアーティファクトがランナーを依存関係に追加します。

ワークフロー

  • 1. ハードコードされた JUnit 依存関係ではなく、テストのソースセットに kotlin("test") を追加します。 マルチプラットフォームモジュールでは commonTest に追加し、JVM/Android モジュールでは testImplementation(kotlin("test")) を使います。フレームワーク固有の要件がない限り、junit:junit や org.junit.jupiter:* も直接追加しないでください — 適切な依存関係はアダプターが追加します。
  • 2. フレームワークに依存しないシンボルをインポートします。 import kotlin.test.*(または必要なものだけ)を使用します。アノテーションは org.junit ではなく、kotlin.test から取得します。
import kotlin.test.Test
import kotlin.test.BeforeTest
import kotlin.test.AfterTest
import kotlin.test.Ignore
import kotlin.test.assertEquals

class CartTest {
    private lateinit var cart: Cart

    @BeforeTest fun setUp() { cart = Cart() }      // == @Before (JUnit4) / @BeforeEach (JUnit5)
    @AfterTest  fun tearDown() { cart.clear() }    // == @After  (JUnit4) / @AfterEach  (JUnit5)

    @Test fun emptyCartTotalsZero() {
        assertEquals(Money.ZERO, cart.total())
    }

    @Ignore @Test fun flaky() { /* skipped — == @Disabled on JUnit5 */ }
}

JVM-JUnit4 では、kotlin.test.Test は org.junit.Test、BeforeTest は org.junit.Before、AfterTest は org.junit.After、Ignore は org.junit.Ignore の typealias です。JUnit5 では、それぞれ org.junit.jupiter.api.Test/BeforeEach/AfterEach/Disabled に対応します。そのため、同じソースをどちらのエンジンでも、また commonTest でも実行できます。

  • 3. 目的に合ったアサーションを選びます。 kotlin.test は意図的に小さく作られており、それぞれの目的に対応する明確な呼び出しが1つずつ用意されています。
目的呼び出し
値が等しい(== による比較)assertEquals(expected, actual, message?)
浮動小数点数が許容誤差内assertEquals(expected, actual, absoluteTolerance, message?)(Double/Float、1.5 以降)
値が等しくないassertNotEquals(illegal, actual, message?)(浮動小数点数用の許容誤差オーバーロードもあります)
同じ/異なるインスタンス(===)assertSame/assertNotSame
null/null ではないassertNull(actual)/assertNotNull(actual) — assertNotNull は非 null の T を返します(assertNotNull(actual) { it -> … } というブロック形式もあります)
指定した型である/指定した型ではないassertIs<T>(value)/assertIsNot<T>(value)(1.5 以降)— assertIs<T> はスマートキャストされた T を返すため、それに続けてアサーションを連結できます
順序を含めて内容が等しいIterable?/Sequence?/Array?/プリミティブ配列には assertContentEquals(expected, actual, message?) を使用します(1.5 以降)。順序を考慮します — Set には代わりに assertEquals を使用してください(assertContentEquals の Set オーバーロードは、曖昧なため非推奨です)
要素が含まれているassertContains(container, element, message?) — Iterable/Sequence/Array/Map(キー)/IntRange/CharSequence など(1.5 以降)
真偽条件assertTrue(actual, message?)/assertFalse(actual, message?)、または条件自体の評価コストが高い場合は assertTrue(message?) { lazyCondition }
無条件の失敗fail(message?) または fail(message?, cause)(1.4 以降)— Nothing を返すため、when/?: で型チェッカーを満たします
「このブロックが X を返す」expect(expected) { block }/expect(expected, message) { block }
ブロックが例外をスローするassertFails { block } は Throwable を返します。assertFailsWith<E> { block }/assertFailsWith(E::class) { block } は、メッセージについてさらにアサーションできるよう、型付きの E を返します
  • 4. 例外の検証には try/catch ではなく assertFailsWith を使用します。 例外が返されるため、そのメッセージや原因を検証できます。
@Test fun withdrawOverdraws() {
    val e = assertFailsWith<InsufficientFundsException> { account.withdraw(BigDecimal(100)) }
    assertEquals("balance is 20", e.message)
}
  • 5. @OnlyInputTypes を活用します — 型の不一致は実行時の失敗ではなく、コンパイルエラーになります。 assertEquals、assertContentEquals、assertContains、assertSame、expect は <@OnlyInputTypes T> を使って宣言されているため、assertEquals(1, "1") はコンパイルできません。「通す」ために Any へ型を広げるのではなく、テストを修正してください。

  • 6. JVM で JUnit 形式の差分表示を復元するには、JUnit アダプターをクラスパスに含めておきます。 kotlin.test は、実行時に ServiceLoader<AsserterContributor> を使って Asserter を解決します。kotlin-test-junit アーティファクトは、org.junit.Assert が存在する場合に assertEquals を org.junit.Assert.assertEquals に委譲する JUnitAsserter を返すコントリビューターを登録します。これにより ComparisonFailure がスローされ、IDE にクリック可能な期待値/実際値の差分が表示されます。コントリビューターがない場合、DefaultAsserter は通常の AssertionError をスローします。そのため、アダプター(kotlin("test") 経由で利用できます)があれば、kotlin.test の失敗は自動的に JUnit の失敗として表示されます。カスタムの Asserter を実装し、AsserterContributor(JVM の場合は META-INF/services/kotlin.test.AsserterContributor)を登録するのは、JUnit 以外のレポート出力先が必要な場合に限ってください。

パターン

パターン:マルチプラットフォームで共有するテストに org.junit.* アノテーションをインポートする
// WRONG — in src/commonTest, or in src/test that you later want to share
import org.junit.Test            // unresolved in commonTest; couples JVM source to JUnit4
import org.junit.Assert.assertEquals
class FooTest {
    @Test fun bar() { assertEquals(2, foo()) }
}
// WRONG because: org.junit is a JVM-JUnit4 dependency. It does not exist in commonMain/commonTest,
// it pins you to JUnit4 (not 5), and it cannot run on Kotlin/JS or Kotlin/Native.
// RIGHT — kotlin.test only; runs everywhere, on whatever engine the build selects
import kotlin.test.Test
import kotlin.test.assertEquals
class FooTest {
    @Test fun bar() { assertEquals(2, foo()) }
}
パターン:例外を手動で検証する
// WRONG
@Test fun parseRejectsGarbage() {
    try {
        parse("garbage")
        fail("expected ParseException")
    } catch (e: ParseException) {
        // ok — but verbose, and a different exception type slips through as a test error not a failure
    }
}
// WRONG because: assertFailsWith already does exactly this, returns the exception for assertions,
// and reports a clear "expected ParseException but was X" message.
// RIGHT
@Test fun parseRejectsGarbage() {
    val e = assertFailsWith<ParseException> { parse("garbage") }
    assertEquals(0, e.offset)
}
パターン:assertEquals の代わりに assertTrue(a == b) を使う
// WRONG
assertTrue(result == expected)
// WRONG because: on failure the message is just "Expected value to be true." — no expected/actual.
// assertEquals reports both values (and a diff via the JUnit asserter).
// RIGHT
assertEquals(expected, result)
// reserve assertTrue for genuine boolean predicates: assertTrue(result.isValid, "result was rejected: ${result.errors}")
パターン:リストの内容を比較する
// WRONG — assertEquals on lists works, but assertEquals on the wrong shape is silent type widening
assertEquals(expected as List<Any>, actual)         // never widen to Any to dodge a type error
// RIGHT — assertContentEquals is the explicit "same elements, same order" check; @OnlyInputTypes keeps it honest
assertContentEquals(listOf(1, 2, 3), service.ids())
// for a Set (order-insensitive) use assertEquals: assertEquals(setOf("a", "b"), service.tags())

必須ルール

  • 必須: org.junit.* を直接インポートするのではなく、kotlin("test")(マルチプラットフォームの場合は commonTest、JVM/Android の場合は testImplementation(kotlin("test")))に依存してください。これにより、テストソースはテストエンジンに依存せず、マルチプラットフォーム間で移植可能になります。
  • 必須: アノテーションは org.junit/org.junit.jupiter.api ではなく、kotlin.test(@Test、@BeforeTest、@AfterTest、@Ignore)からインポートしてください。
  • 必須: 何でも assertTrue(...) にまとめるのではなく、それぞれの用途に応じて assertEquals/assertNotEquals/assertContentEquals/assertIs/assertContains を使用してください。専用の呼び出しを使うと、期待値と実際の値が診断情報に表示されます。
  • 必須: 例外の確認には assertFailsWith<E> { … } を使用してください(メッセージや原因のアサーションに使える例外が返されます)。try/catch と fail の組み合わせは使用しないでください。
  • 禁止: @OnlyInputTypes のコンパイルエラーを回避するために、引数の型を広げないでください(as Any、as List<Any>)。このエラーは実際の不一致を検出しています。
  • 禁止: Set に assertContentEquals を呼び出さないでください(非推奨で、意味が曖昧です)。順序なしのセットの等価性には assertEquals を使用してください。
  • 禁止: JVM で kotlin-test-junit/kotlin-test-junit5 アダプターを削除しないでください。クラスパスに AsserterContributor がないと、失敗時に差分が表示されず、単なる AssertionError になってしまいます。
  • 推奨: テストでは x!!/x as T より assertNotNull(x)/assertIs<T>(x) を使用してください。NPE/ClassCastException よりも明確なメッセージで失敗し、絞り込まれた値が返されます。
  • 推奨: 条件やメッセージの計算にコストがかかる場合は、遅延ブロック形式の assertTrue(message) { expensiveCondition() } を使用してください。

検証

  • マルチプラットフォーム/共有を目的としたソースセットに import org.junit.* または import org.junit.jupiter.api.* がないこと。grep -rn 'import org.junit' src/commonTest src/test の結果が空であること(または、プラットフォーム固有のものとしてのみ正当化できること)。
  • ./gradlew :module:dependencies --configuration testRuntimeClasspath(JVM)で、kotlin("test") の機能を通じて解決された kotlin-test-junit または kotlin-test-junit5 が表示されること。
  • テストクラスで、kotlin.test の @Test/@BeforeTest/@AfterTest を使用していること。
  • 例外ケースで、try/catch ではなく assertFailsWith<…> { … } を使用していること。
  • アサーションをコンパイルするために as Any/as List<Any> で型を広げていないこと。
  • JVM モジュールで意図的に失敗させた assertEquals が、JUnit の ComparisonFailure(クリック可能な差分)として表示されること。これは AsserterContributor がクラスパスにある証拠です。

参考資料

  • kotlinlang.org/api/latest/kotlin.test/ — kotlin.test API リファレンス:すべての assert* 関数、expect、fail、アノテーション、Asserter/AsserterContributor/DefaultAsserter。
  • kotlinlang.org/docs/jvm-test-using-junit.html および kotlinlang.org/docs/multiplatform-run-tests.html — JVM 上での kotlin("test") の使用(JUnit4/JUnit5 の選択)と、マルチプラットフォームの各ターゲットでの使用方法。
  • libraries/kotlin.test/annotations-common/src/main/kotlin/kotlin.test/Annotations.kt(Kotlin リポジトリ) — expect annotation class Test/Ignore/BeforeTest/AfterTest。JVM の actual typealias は libraries/kotlin.test/junit/src/main/kotlin/Annotations.kt(→ org.junit.*)および libraries/kotlin.test/junit5/src/main/kotlin/Annotations.kt(→ org.junit.jupiter.api.*)にあります。
  • libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt(Kotlin リポジトリ) — assertTrue/assertFalse/assertEquals/assertNotEquals/assertSame/assertNotSame/assertNull/assertNotNull/assertIs/assertIsNot/assertContains/assertContentEquals/fail/expect/assertFails/assertFailsWith のシグネチャ、@OnlyInputTypes アノテーション、Asserter/AsserterContributor インターフェース。
  • libraries/kotlin.test/jvm/src/main/kotlin/AsserterLookup.kt および libraries/kotlin.test/junit/src/main/kotlin/JUnitSupport.kt(Kotlin リポジトリ) — ServiceLoader<AsserterContributor> による検索。JUnitContributor/JUnitAsserter は、org.junit.Assert が存在する場合はそれに処理を委譲し、存在しない場合は DefaultAsserter を使用します。
  • 関連セット:../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md — JUnit4 ランナー/AndroidJUnit4 スタック。kotlin.test のアサーションは、そのままこれらのテストに組み込めます。
  • 関連セット:../../../jvm-tests/coroutines/testing-coroutines-with-runtest/SKILL.md — runTest が周囲のスコープであり、その内部で kotlin.test が assert* 呼び出しを提供します。
  • 関連セット:../../../jvm-tests/coroutines/testing-flows-with-turbine/SKILL.md — Turbine の awaitItem() と kotlin.test の assertEquals を組み合わせて使います。
  • 関連セット:../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md — これらのテストの配置場所(src/test/、src/commonTest/)と、testImplementation と androidTestImplementation の使い分け。
  • 関連セット:../../../fundamentals/doubles/picking-test-doubles/SKILL.md — アサーションで検証するフェイク/スタブ。

コメント