Skip to content

自定义 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