organizing-test-source-sets について
Android 開発者向けに、テストの配置先や Gradle の依存関係設定、旧 sharedTest 構成からの移行を説明するテストスキルです。ローカル JVM、計装テスト、ホスト/デバイスのソースセットを区別します。
主な機能
- 01src/test/ と src/androidTest/ の使い分け
- 02testImplementation と androidTestImplementation の説明
- 03テストマニフェスト向け debugImplementation の解説
- 04sharedTest の移行方法
- 05androidHostTest と androidDeviceTest の説明
活用シーン
- 01Robolectric テストの配置先選び
- 02誤った Gradle テスト依存関係の修正
- 03AGP 7.2 以降での sharedTest からの移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ skydoves/ android-testing-skills --skill organizing-test-source-setsインストール後、Claude Code で/organizing-test-source-setsを入力して読み込まれたか確認します。
詳細
テストのソースセットの整理 — src/test/、src/androidTest/、KMP の分割
テストファイルをどこに置くかによって、実行に使われる JVM、参照できる依存関係、テスト APK に含まれるかどうかが決まります。ほとんどのモジュールで必要なのは src/test/ と src/androidTest/ だけです。ホストテストとデバイステストでヘルパーを共有するモジュールでは、これまで src/sharedTest/ が使われていました(Codelab 独自の規約であり、Google が公式に文書化したものではありません)。しかし、AGP 7.2 以降で動作しなくなり、Compose 自体も KMP 形式の androidHostTest / androidDeviceTest の分割に移行しました。このスキルでは、エージェントが自信を持ってテストファイルを配置できるよう、この対応関係をまとめています。
このスキルを使用する場合
- ユーザーが「テストはどこに置けばよいですか?」または「
src/test/とsrc/androidTest/のどちらですか?」と尋ねた場合。 - ユーザーが「
testImplementationとandroidTestImplementationとdebugImplementationの違いは何ですか?」と尋ねた場合。 - ユーザーから
Unresolved reference: createComposeRuleの報告があり、エージェントが依存関係の設定先が誤っていると考えた場合。 - AGP 7.2 以降で
sourceSets { test.java.srcDir(...) }ブロックが動作しなくなり、ユーザーがsrc/sharedTest/から移行しようとしている場合。 - ユーザーが
androidHostTest/androidDeviceTestに言及し、非 KMP モジュールでの設定方法を尋ねた場合。 - ユーザーが「
src/androidTest/にある Robolectric テストはなぜですか?」と尋ねた場合 —src/test/に置くべきです。
このスキルを使用しない場合
- ユーザーが Compose テスト(
ui-test-junit4、ui-test-manifest)の Gradle 依存関係を設定している場合 —../../../compose/setup/configuring-test-dependencies/SKILL.mdを使用してください。 - ユーザーがテストのスコープ(小・中・大)を選んでいる場合 —
../../concepts/understanding-the-testing-pyramid/SKILL.mdを使用してください。 - ユーザーが
AndroidJUnitRunner自体を設定している場合 —../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.mdを使用してください。 - ユーザーが JVM テスト用の JUnit4 ランナーを設定している場合 —
../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.mdを使用してください。
前提条件
- Android Gradle Plugin(
com.android.applicationまたはcom.android.library)を使用するモジュール。 - Gradle の設定名(
testImplementation、androidTestImplementation、debugImplementation)に関する知識。 - JVM テストはホストの JVM で実行され、インストルメンテーションテストはデバイスまたはエミュレーターで実行されることの理解。(
../../concepts/understanding-the-testing-pyramid/SKILL.mdを参照してください。)
Google が文書化している 2 つのソースセット
src/test/ — ローカル JVM テスト
/training/testing/local-tests によると:
"デフォルトでは、ローカル単体テストのソースファイルは
module-name/src/test/に配置されます。このディレクトリは、Android Studio で新しいプロジェクトを作成すると、すでに存在しています。" —developer.android.com/training/testing/local-tests
"ローカルテストは、Android デバイスやエミュレーターではなく、ワークステーション上で直接実行されます。そのため、ローカルの Java 仮想マシン(JVM)を使用します。" —
developer.android.com/training/testing/local-tests
testImplementation によって制御されます。./gradlew test または ./gradlew testDebugUnitTest で実行します。Android デバイスもエミュレーターも不要です。Robolectric がシミュレートしない限り、Android フレームワークは利用できません。
src/androidTest/ — インストルメンテーションテスト
/training/testing/instrumented-tests によると:
"Android Studio プロジェクトでは、インストルメンテーションテストのソースファイルを
module-name/src/androidTest/java/に保存します。このディレクトリは新しいプロジェクトを作成するとすでに存在し、サンプルのインストルメンテーションテストが含まれています。" —developer.android.com/training/testing/instrumented-tests
androidTestImplementation によって制御されます。./gradlew connectedAndroidTest または ./gradlew connectedDebugAndroidTest で実行します。接続済みのデバイスまたはエミュレーターが必要です。CORPUS §H.2 に従って AndroidJUnitRunner を使用します。
このページでは、testImplementation と androidTestImplementation の使い分けも明示されています:
"注:
testImplementationはローカルテスト用の依存関係を追加し、androidTestImplementationはインストルメンテーションテスト用の依存関係を追加します。" —developer.android.com/training/testing/local-tests
構成マトリクス
Configuration Source set Variant Ships in
-----------------------------------------------------------------------------------
implementation main all release + debug + tests
api main all release + debug + tests
debugImplementation debug only debug debug APK + androidTest APK
releaseImplementation release only release release APK only
testImplementation src/test/ all (host) JVM test classpath
testDebugImplementation src/test/ debug (host) JVM test classpath, debug only
androidTestImplementation src/androidTest/ all (device) androidTest APK
androidTestUtil n/a all orchestrator/util install (NOT classpath)
エージェントが区別しておくべき3つの構成です。
testImplementation— JVM(ホスト)テスト用です。Mockito、MockK、Robolectric、kotlinx-coroutines-test、Turbineが含まれます。androidTestImplementation— インストルメンテーション(デバイス)テスト用です。Espresso、UiAutomator、AndroidJUnitRunner、Composeui-test-junit4が含まれます。debugImplementation— メインのdebugバリアント用です。テスト APK は debug APK をベースにビルドされ、マニフェストエントリをマージする必要があるため、androidx.compose.ui:ui-test-manifestとandroidx.fragment:fragment-testing-manifestで使用されます。
androidx.compose.ui:ui-test-manifestは、androidTestImplementationではなく、必ずdebugImplementationに指定してください。これを強制するLintチェック(TestManifestGradleConfiguration)については、../../../compose/setup/configuring-test-dependencies/SKILL.mdを参照してください。
src/sharedTest/ — コミュニティの慣例であり、Googleのドキュメントには記載なし
tasks/research/R8-android-fundamentals.mdによると:
src/sharedTest/は、対象となる6つのdeveloper.android.com/training/testing/...ページにも、取得済みのHiltテスト/rulesページにも、名前を挙げて記載されていません。
このパターンは、Google独自のArchitecture BlueprintsサンプルとAdvanced Android Testing Codelabで使われたものです。GradleのsourceSetsを使って、同じJava/KotlinフォルダーをtestとandroidTestの両方に組み込みます。
// pre-AGP 7.2 — the classic sharedTest wiring
android {
sourceSets {
getByName("test").java.srcDir("src/sharedTest/java")
getByName("androidTest").java.srcDir("src/sharedTest/java")
}
}
このパターンが存在した理由:テストを一度書くだけで、CIでは高速なRobolectricホストテストとして、エミュレーターでは高忠実度のインストルメンテーションテストとして実行できます。テストクラスではandroidx.test.ext.junit.runners.AndroidJUnit4を使用します(CORPUS §G.6によると、これは両方の環境で適切に処理を振り分けます)。
このパターンが使えなくなった理由:AGP 7.2以降では、同じソースディレクトリをビルド時に複数のソースセット間で共有できません。ビルド自体が拒否され、重複したsrcDir(...)呼び出しを示すエラーが表示されます(失敗するのはAGPによるビルド時の拒否であり、実行時のLinkageErrorではありません)。https://github.com/robolectric/robolectric/issues/7432 で追跡されています(Robolectricメンテナースレッドに再現例とAGPバージョン表があります)。多くのモジュールでは、次のいずれかの方法を採用しています。
- テストの共有をやめ、重複を受け入れました。
- テストヘルパーを
testImplementationとandroidTestImplementation経由で提供する、別のGradleサブモジュール(:core-testing)に移行しました。 - KMP形式のソースセット名(
androidHostTest/androidDeviceTest)を採用しました。
KMP形式のandroidHostTest/androidDeviceTest
CORPUS §C(「ソースセットの慣例(KMP)」)によると、Compose UIテストでは内部的にこの構成を使用しています。
commonTest
└── androidCommonTest (helpers shared by host + device)
├── androidDeviceTest (instrumentation APK on emulator/device)
└── androidHostTest (Robolectric on JVM)
実際のandroidxの例:compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.ktはホスト(Robolectric)テストで、compose/material3/material3/src/androidDeviceTest/.../SwitchTest.ktはデバイステストです。どちらも共通ヘルパーをcommonTestからコンパイルします。
KMPを使わないAndroid専用モジュールでは、別々のサブモジュールを使って同じ効果を得られます。
// :feature:home/build.gradle.kts — production module
plugins { id("com.android.library") }
dependencies {
testImplementation(project(":feature:home-testing"))
androidTestImplementation(project(":feature:home-testing"))
}
// :feature:home-testing/build.gradle.kts — pure Kotlin or Android-library "test fixtures"
plugins { id("java-library") } // or com.android.library if Android types needed
:feature:home-testingモジュールには、FakeUserRepository、MainDispatcherRule、カスタムマッチャーなど、ホストテストとデバイステストの両方で必要なものを配置します。
代替案:AGP 7.0以降のtestFixturesフラグ(android { testFixtures { enable = true } })を使う方法です。これにより、testFixturesImplementation用のtestFixtures() Gradleアーティファクトが公開され、兄弟モジュールのtestImplementation/androidTestImplementationから利用できます。
sharedTestについてdeveloper.android.com/training/testing/...を引用してはいけません。該当ページにはその記載がありません。代わりに、Architecture Blueprintsのリポジトリまたはtasks/research/R8-android-fundamentals.mdを必ず引用してください。
ファイル配置の判断ツリー
Where does this test go?
│
├── Pure Kotlin/JVM logic, no Android framework → src/test/
│ (testImplementation deps)
│
├── Robolectric (Android framework simulated on JVM) → src/test/
│ (testImplementation deps + Robolectric)
│ NOT src/androidTest/.
│
├── ViewModel + coroutines + fakes → src/test/
│
├── Real Room database integration → src/androidTest/
│ (small instrumented test per /fundamentals)
│
├── Espresso UI test → src/androidTest/
├── UiAutomator cross-app test → src/androidTest/
├── Compose UI test against real Activity → src/androidTest/
│ + debugImplementation("...:ui-test-manifest")
│
├── Compose UI test on Robolectric → src/test/
│ (testImplementation("...:ui-test-junit4") + Robolectric)
│
├── Test helper (FakeRepository, MainDispatcherRule) → :core-testing module OR src/sharedTest/
│ (with the AGP 7.2+ caveats above)
パターン
パターン:誤りと正しい例 — testImplementationとandroidTestImplementation
// WRONG — Espresso on testImplementation
dependencies {
testImplementation("androidx.test.espresso:espresso-core:3.7.0")
testImplementation("androidx.test.ext:junit:1.3.0")
}
// WRONG because: Espresso requires a real or simulated Android device. testImplementation
// only puts the JAR on the JVM classpath. The test compiles but ClassNotFoundException /
// IllegalStateException at runtime when Espresso looks for an Instrumentation instance.
// RIGHT — Espresso on androidTestImplementation
dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
}
パターン:誤りと正しい例 — src/androidTest/内のRobolectric
// WRONG
// src/androidTest/java/com/example/SomeRobolectricTest.kt
@RunWith(RobolectricTestRunner::class)
class SomeRobolectricTest { /* ... */ }
// WRONG because: Robolectric is a JVM Android simulator (per /fundamentals: "Big local test:
// You can use an Android simulator that runs locally, such as Robolectric"). It belongs in
// src/test/. Putting it in src/androidTest/ either fails to find Robolectric (which is on
// testImplementation) or runs it on the device JVM where its shadow mechanism does not work.
// RIGHT
// src/test/java/com/example/SomeRobolectricTest.kt
@RunWith(AndroidJUnit4::class) // dispatches to Robolectric on JVM
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE])
class SomeRobolectricTest { /* ... */ }
パターン:誤りと正しい例 — ui-test-manifestの構成
// WRONG
dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.compose.ui:ui-test-manifest") // wrong config
}
// WRONG because: ui-test-manifest contributes <activity android:name="ComponentActivity">
// to the merged debug APK manifest. On androidTestImplementation, the merger does not pick
// it up; createComposeRule() crashes with ActivityNotFoundException. Lint also flags this
// as TestManifestGradleConfiguration (WARNING).
// RIGHT
dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
Lintの詳細については、../../../compose/setup/configuring-test-dependencies/SKILL.mdを参照してください。
パターン:AGP 7.2+ の sharedTest 移行
// BROKE on AGP 7.2+
android {
sourceSets {
getByName("test").java.srcDir("src/sharedTest/java")
getByName("androidTest").java.srcDir("src/sharedTest/java")
}
}
// MIGRATION OPTION A — extract a :core-testing sub-module
// :core-testing/build.gradle.kts
plugins { id("java-library") }
// classes here are consumed by both test sourcesets in dependent modules
// :feature:home/build.gradle.kts
dependencies {
testImplementation(project(":core-testing"))
androidTestImplementation(project(":core-testing"))
}
// MIGRATION OPTION B — testFixtures
android {
testFixtures { enable = true }
}
// src/testFixtures/java/... contains FakeUserRepository, MainDispatcherRule
// dependent module
dependencies {
testImplementation(testFixtures(project(":core")))
androidTestImplementation(testFixtures(project(":core")))
}
// MIGRATION OPTION C — KMP source-set naming
// In a KMP Android module:
kotlin {
androidTarget {
compilations.getByName("debug") { /* ... */ }
}
sourceSets {
val androidHostTest by getting // Robolectric, JVM
val androidDeviceTest by getting // emulator/device
val androidCommonTest by getting // helpers shared by both
androidHostTest.dependsOn(androidCommonTest)
androidDeviceTest.dependsOn(androidCommonTest)
}
}
Compose 自体は内部でオプション C を使用しています(CORPUS §B「ソースセットの規約」による)。
モジュールレベルの Gradle サンプル
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.feature.home"
compileSdk = 35
defaultConfig {
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
testOptions {
unitTests.isIncludeAndroidResources = true // required for Robolectric
animationsDisabled = true // hermetic instrumented tests
}
}
dependencies {
// production
implementation("androidx.activity:activity-compose")
// host (JVM) tests — src/test/
testImplementation("junit:junit:4.13.2")
testImplementation("androidx.test:core:1.7.0")
testImplementation("androidx.test.ext:junit:1.3.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.0")
testImplementation("org.mockito:mockito-core:5.13.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.robolectric:robolectric:4.13")
testImplementation("app.cash.turbine:turbine:1.1.0")
// instrumented (device) tests — src/androidTest/
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
// debug-only — merged manifest entries for test scaffolds
debugImplementation("androidx.compose.ui:ui-test-manifest")
debugImplementation("androidx.fragment:fragment-testing-manifest:1.8.5")
// orchestrator (note: androidTestUtil, not androidTestImplementation)
androidTestUtil("androidx.test:orchestrator:1.6.1")
}
必須ルール
- 必須: 純粋な Kotlin/JVM テストは
src/test/に配置し、依存関係にはtestImplementationを使用してください。 - 必須: Robolectric テストは
src/test/に配置してください。Robolectric は JVM 上の Android シミュレーターであり、インストルメント化されたランナーではありません。 - 必須: 実機/エミュレーターのテストは
src/androidTest/に配置し、依存関係にはandroidTestImplementationを使用してください。 - 必須:
androidx.compose.ui:ui-test-manifestとandroidx.fragment:fragment-testing-manifestはdebugImplementationに指定し、androidTestImplementationには決して指定しないでください。 - 禁止:
src/sharedTest/の根拠としてdeveloper.android.com/training/testing/...を引用しないでください。そこには記載されていません。Architecture Blueprints リポジトリまたはtasks/research/R8-android-fundamentals.mdを引用してください。 - 禁止: Espresso、UiAutomator、または
ui-test-junit4をtestImplementationに追加しないでください。コンパイルはできますが、Instrumentationがない場合は実行時に失敗します。 - 推奨: テストヘルパーを共有する場合は、AGP 7.2 で問題が起きやすい
sharedTestソースセットではなく、:core-testingGradle サブモジュールを切り出すか、android.testFixtures.enable = trueを使用してください。 - 推奨: KMP モジュールでは Compose に倣い、ヘルパーには
androidCommonTest、Robolectric にはandroidHostTest、エミュレーターのテストにはandroidDeviceTestを使用してください。 - 推奨:
src/test/ではバッククォートで囲んだテストメソッド名を自由に使用し、src/androidTest/では/training/testing/instrumented-testsに従って API 30 以降のデバイスに限定してください。
検証
-
src/androidTest/内に Robolectric テストがないことを確認してください。find src/androidTest -name '*.kt' -exec grep -l 'org.robolectric' {} +を実行し、出力が空であることを確認してください。 -
src/test/内に Espresso/UiAutomator のインポートがないことを確認してください。find src/test -name '*.kt' -exec grep -l 'androidx.test.espresso\|androidx.test.uiautomator' {} +を実行し、出力が空であることを確認してください。 -
androidx.compose.ui:ui-test-manifestがdebugImplementationにのみ指定されていることを確認してください。./gradlew :<module>:lintを実行し、TestManifestGradleConfiguration警告がないことを確認してください。 -
./gradlew :<module>:testDebugUnitTestで JVM テストが実行され、connectedDebugAndroidTestが呼び出されないことを確認してください。 -
./gradlew :<module>:connectedDebugAndroidTestでsrc/androidTest/内のテストのみが実行されることを確認してください(--infoの出力で確認)。 - 共有ヘルパーがある場合は、
:core-testingサブモジュール、testFixtures、またはandroidCommonTestに配置してください。sourceSets { ... srcDir(...) }で配線した文字どおりのsrc/sharedTest/フォルダーには配置しないでください。 - ホストテストが
R.*を参照する場合は、testOptions.unitTests.isIncludeAndroidResources = trueが設定されていることを確認してください。
参考資料
developer.android.com/training/testing/local-tests—src/test/、testImplementation、「ローカルテストは自分のワークステーション上で直接実行されます」。developer.android.com/training/testing/instrumented-tests—src/androidTest/、androidTestImplementation、AndroidJUnitRunner、バッククォートに関する注意事項。developer.android.com/training/testing/fundamentals— 「大規模なローカルテスト:Robolectric など、ローカルで実行される Android シミュレーターを使用できます」。tasks/research/R8-android-fundamentals.md— 引用文そのまま。src/sharedTest/が対象範囲内の/training/testing/...の 6 ページのいずれにも記載されていないことを記録しています。- Architecture Blueprints(
sharedTestパターンの起源):https://github.com/android/architecture-samples - AGP のソースセットに関するドキュメント:https://developer.android.com/build/build-variants#sourcesets
testFixturesフラグ:https://developer.android.com/build/dependencies#test-fixtures- CORPUS §C「ソースセットの規約(KMP)」— Compose 内部の
androidHostTest/androidDeviceTestの構成。 compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt— androidx における標準的なホストテストのひな形。compose/material3/material3/src/androidDeviceTest/.../SwitchTest.kt— 標準的なデバイステストのひな形。- 関連するスキル:
../../concepts/understanding-the-testing-pyramid/SKILL.md、../../concepts/choosing-what-to-test/SKILL.md、../../doubles/picking-test-doubles/SKILL.md、../applying-testing-strategies/SKILL.md。 - カテゴリー横断:
../../../compose/setup/configuring-test-dependencies/SKILL.md、../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md、../../../jvm-tests/robolectric/using-robolectric-correctly/SKILL.md、../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md、../../../adb/tests/running-instrumented-tests-via-adb/SKILL.md。