writing-tests-with-kotlin-test について
Kotlin のテスト作成者向けに、kotlin.test のアサーション、アノテーション、Gradle 設定を解説するスキルです。JVM、Android、JS、Kotlin Multiplatform で共有できる、テストフレームワーク非依存の API を扱います。
主な機能
- 01ターゲット共通のアサーションとアノテーション
- 02例外・型アサーションのパターン
- 03@OnlyInputTypes によるコンパイル時チェック
- 04Asserter 拡張ポイントの解説
- 05Gradle 依存関係の設定
活用シーン
- 01commonTest で共有テストを作成
- 02複数ターゲットでテストソースを再利用
- 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.testAPI リファレンス:すべての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— アサーションで検証するフェイク/スタブ。