自定义 SourceSets
源:Android 官方文档 - Configure Build Variants
创建自定义 SourceSet 实现代码隔离和测试分层。
SourceSet 概念
什么是 SourceSet
SourceSet:一组源代码和资源文件的集合。
默认 SourceSets:
main:主代码test:单元测试androidTest:Android 测试
为什么需要自定义 SourceSet
使用场景:
- 集成测试(比单元测试重,比UI测试轻)
- 性能测试
- 多环境代码(dev/staging/prod)
- 模块化代码组织
创建自定义 SourceSet
基本创建
build.gradle.kts:
kotlin
android {
sourceSets {
create("integrationTest") {
java.srcDir("src/integrationTest/kotlin")
resources.srcDir("src/integrationTest/resources")
}
}
}目录结构:
src/
├── main/
│ └── kotlin/
├── test/
│ └── kotlin/
├── androidTest/
│ └── kotlin/
└── integrationTest/ ← 新增
├── kotlin/
└── resources/配置依赖
kotlin
dependencies {
// 集成测试依赖
"integrationTestImplementation"(libs.junit)
"integrationTestImplementation"(libs.kotlin.test)
"integrationTestImplementation"(libs.mockk)
// 包含 main 代码
"integrationTestImplementation"(sourceSets["main"].output)
}创建测试任务
kotlin
tasks.register<Test>("integrationTest") {
description = "运行集成测试"
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
// 必须在单元测试之后
shouldRunAfter("test")
}
// 添加到 check 任务
tasks.named("check") {
dependsOn("integrationTest")
}Android 集成测试
完整配置
kotlin
android {
sourceSets {
create("integrationTest") {
// 源代码
java.srcDir("src/integrationTest/kotlin")
// 资源
res.srcDir("src/integrationTest/res")
assets.srcDir("src/integrationTest/assets")
// Manifest
manifest.srcFile("src/integrationTest/AndroidManifest.xml")
// 依赖 main
compileClasspath += sourceSets["main"].output
runtimeClasspath += sourceSets["main"].output
}
}
testOptions {
unitTests.all {
it.testLogging {
events("passed", "skipped", "failed")
}
}
}
}
dependencies {
"integrationTestImplementation"(libs.androidx.test.core)
"integrationTestImplementation"(libs.androidx.test.runner)
"integrationTestImplementation"(libs.robolectric)
}AndroidManifest.xml
src/integrationTest/AndroidManifest.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.example.app.integrationtest">
<uses-sdk android:minSdk="24" android:targetSdk="34" />
</manifest>多环境 SourceSet
Flavor SourceSets
kotlin
android {
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
}
create("prod") {
dimension = "environment"
}
}
}目录结构:
src/
├── main/
├── dev/
│ └── kotlin/
│ └── config/
│ └── ApiConfig.kt // dev API 配置
├── staging/
│ └── kotlin/
│ └── config/
│ └── ApiConfig.kt // staging API 配置
└── prod/
└── kotlin/
└── config/
└── ApiConfig.kt // prod API 配置ApiConfig.kt(dev):
kotlin
package com.example.config
object ApiConfig {
const val BASE_URL = "https://dev-api.example.com"
const val DEBUG = true
}实战案例
案例1:集成测试 SourceSet
完整配置:
kotlin
// build.gradle.kts
android {
namespace = "com.example.app"
sourceSets {
create("integrationTest") {
java.srcDir("src/integrationTest/kotlin")
manifest.srcFile("src/integrationTest/AndroidManifest.xml")
}
}
}
dependencies {
// 主依赖
implementation(libs.androidx.core.ktx)
// 集成测试依赖
"integrationTestImplementation"(libs.junit)
"integrationTestImplementation"(libs.androidx.test.core)
"integrationTestImplementation"(libs.robolectric)
"integrationTestImplementation"(libs.mockWebServer)
// 复用单元测试的工具
"integrationTestImplementation"(libs.truth)
"integrationTestImplementation"(libs.mockk)
}
tasks.register<Test>("integrationTest") {
description = "运行集成测试"
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
showStandardStreams = true
}
}
tasks.named("check") {
dependsOn("integrationTest")
}集成测试示例:
kotlin
// src/integrationTest/kotlin/RepositoryIntegrationTest.kt
@RunWith(RobolectricTestRunner::class)
class RepositoryIntegrationTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var repository: UserRepository
@Before
fun setup() {
mockWebServer = MockWebServer()
mockWebServer.start()
val api = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.build()
.create(UserApi::class.java)
repository = UserRepository(api)
}
@Test
fun `test fetch users`() = runTest {
// Mock 响应
mockWebServer.enqueue(
MockResponse()
.setBody("""[{"id":1,"name":"Alice"}]""")
.setResponseCode(200)
)
// 执行
val users = repository.fetchUsers()
// 断言
assertEquals(1, users.size)
assertEquals("Alice", users[0].name)
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
}案例2:性能测试 SourceSet
kotlin
android {
sourceSets {
create("benchmark") {
java.srcDir("src/benchmark/kotlin")
}
}
}
dependencies {
"benchmarkImplementation"(libs.androidx.benchmark.junit4)
"benchmarkImplementation"(libs.androidx.test.runner)
}
tasks.register<Test>("benchmark") {
description = "运行性能测试"
group = "verification"
testClassesDirs = sourceSets["benchmark"].output.classesDirs
classpath = sourceSets["benchmark"].runtimeClasspath
}测试分层
测试金字塔
UI Tests (androidTest)
/\
/ \
/ \
/ Int \ (integrationTest)
/ Test \
/----------\
/ Unit \ (test)
/ Tests \
/________________\分层策略
单元测试(test):
- 纯逻辑测试
- 无依赖
- 快速执行
集成测试(integrationTest):
- 模块间交互
- Mock 外部依赖
- 中等速度
UI 测试(androidTest):
- 完整流程
- 真实设备
- 慢速执行
最佳实践
命名规范:
kotlin
sourceSets {
create("integrationTest") // ✅ 小驼峰
create("performanceTest")
}依赖继承:
kotlin
dependencies {
// 集成测试继承 test 的依赖
"integrationTestImplementation"(configurations["testImplementation"])
}目录结构:
src/
├── main/kotlin/
├── test/kotlin/ # 单元测试
├── integrationTest/kotlin/ # 集成测试
└── androidTest/kotlin/ # UI 测试任务组织:
kotlin
tasks.register("testAll") {
dependsOn("test", "integrationTest")
}CI 集成:
yaml
- name: Unit Tests
run: ./gradlew test
- name: Integration Tests
run: ./gradlew integrationTest
- name: UI Tests
run: ./gradlew connectedAndroidTest