android-tdd
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAndroid Test-Driven Development (TDD)
Android测试驱动开发(TDD)
Overview
概述
TDD is a development process where you write tests before feature code, following the Red-Green-Refactor cycle. Every feature starts with a failing test, gets minimal implementation, then is refined.
Core Principle: No production code without a failing test first.
Icon Policy: If UI code is generated as part of TDD, use custom PNG icons and maintain (see ).
PROJECT_ICONS.mdandroid-custom-iconsReport Table Policy: If UI tests cover reports that can exceed 25 rows, the UI must use table layouts (see ).
android-report-tablesTDD是一种先编写测试代码,再编写功能代码的开发流程,遵循红-绿-重构循环。每个功能都从编写一个失败的测试开始,完成最小化实现后再进行优化。
核心原则: 没有先编写失败的测试,就不要编写生产代码。
图标规范: 如果UI代码是通过TDD生成的,请使用自定义PNG图标并维护(参考)。
PROJECT_ICONS.mdandroid-custom-icons报表表格规范: 如果UI测试覆盖的报表行数可能超过25行,UI必须使用表格布局(参考)。
android-report-tablesQuick Reference
快速参考
| Topic | Reference File | When to Use |
|---|---|---|
| TDD Workflow | | Step-by-step Red-Green-Refactor with examples |
| Testing by Layer | | Unit, integration, persistence, network, UI tests |
| Advanced Techniques | | Factories, behavior verification, LiveData/Flow |
| Tools & CI Setup | | Dependencies, CI pipelines, test configuration |
| Team Adoption | | Legacy code, team onboarding, troubleshooting |
| 主题 | 参考文件 | 使用场景 |
|---|---|---|
| TDD工作流 | | 带示例的红-绿-重构分步指南 |
| 分层测试 | | 单元测试、集成测试、持久化测试、网络测试、UI测试 |
| 高级技巧 | | 工厂模式、行为验证、LiveData/Flow相关测试 |
| 工具与CI配置 | | 依赖配置、CI流水线、测试环境设置 |
| 团队落地 | | 遗留代码处理、团队培训、问题排查 |
The Red-Green-Refactor Cycle
红-绿-重构循环
1. RED → Write a failing test for desired behavior
2. GREEN → Write MINIMUM code to make it pass
3. REFACTOR → Clean up while keeping tests green
4. REPEAT → Next behaviorCritical Rules:
- Never skip the Red phase (verify the test actually fails)
- Never write more code than needed in Green phase
- Never refactor with failing tests
- Each cycle should take minutes, not hours
1. RED → 为期望的行为编写一个失败的测试
2. GREEN → 编写最少的代码让测试通过
3. REFACTOR → 在保持测试通过的前提下优化代码
4. REPEAT → 下一个功能点关键规则:
- 绝不要跳过红阶段(验证测试确实会失败)
- 绿阶段绝不要编写超出需求的代码
- 绝不要在测试失败时进行重构
- 每个循环应耗时数分钟,而非数小时
Test Pyramid (70/20/10)
测试金字塔(70/20/10)
/ UI \ 10% - Espresso, end-to-end flows
/--------\
/ Integra- \ 20% - ViewModel+Repository, Room, API
/ tion \
/--------------\
/ Unit Tests \ 70% - Pure Kotlin, fast, isolated
/==================\| Type | Speed | Scope | Location | Tools |
|---|---|---|---|---|
| Unit | <1ms each | Single class/method | | JUnit, Mockito |
| Integration | ~100ms each | Component interactions | | JUnit, Robolectric |
| UI | ~1s each | User flows | | Espresso, Compose Testing |
/ UI \\ 10% - Espresso、端到端流程测试
/--------\\
/ 集成测试 \\ 20% - ViewModel+Repository、Room、API测试
/ \\
/--------------\\
/ 单元测试 \\ 70% - 纯Kotlin代码、快速、隔离
/==================\\| 测试类型 | 速度 | 测试范围 | 代码位置 | 工具 |
|---|---|---|---|---|
| 单元测试 | 每个<1毫秒 | 单个类/方法 | | JUnit、Mockito |
| 集成测试 | 每个~100毫秒 | 组件间交互 | | JUnit、Robolectric |
| UI测试 | 每个~1秒 | 用户流程 | | Espresso、Compose Testing |
TDD Workflow for Android Features
Android功能的TDD工作流
Step 1: Define the Requirement
步骤1:定义需求
Start with a clear user story or acceptance criteria:
As a user, I want to add items to my cart so I can purchase them later.
从清晰的用户故事或验收标准开始:
作为用户,我希望能将商品加入购物车,以便后续购买。
Step 2: Write the Failing Test (Red)
步骤2:编写失败的测试(红阶段)
kotlin
@Test
fun addItemToCart_increasesCartCount() {
val cart = ShoppingCart()
cart.addItem(Product("Phone", 999.99))
assertEquals(1, cart.itemCount)
}Run it. It must fail (class doesn't exist yet).
kotlin
@Test
fun addItemToCart_increasesCartCount() {
val cart = ShoppingCart()
cart.addItem(Product("Phone", 999.99))
assertEquals(1, cart.itemCount)
}运行测试,必须失败(此时类还不存在)。
ShoppingCartStep 3: Write Minimal Code (Green)
步骤3:编写最少代码(绿阶段)
kotlin
class ShoppingCart {
private val items = mutableListOf<Product>()
fun addItem(product: Product) { items.add(product) }
val itemCount: Int get() = items.size
}Run test. It passes. Stop writing code.
kotlin
class ShoppingCart {
private val items = mutableListOf<Product>()
fun addItem(product: Product) { items.add(product) }
val itemCount: Int get() = items.size
}运行测试,测试通过后停止编写代码。
Step 4: Add Next Test, Then Refactor
步骤4:添加下一个测试,然后重构
kotlin
@Test
fun addMultipleItems_calculatesTotal() {
val cart = ShoppingCart()
cart.addItem(Product("Phone", 999.99))
cart.addItem(Product("Case", 29.99))
assertEquals(1029.98, cart.totalPrice, 0.01)
}Implement , then refactor both test and production code.
totalPricekotlin
@Test
fun addMultipleItems_calculatesTotal() {
val cart = ShoppingCart()
cart.addItem(Product("Phone", 999.99))
cart.addItem(Product("Case", 29.99))
assertEquals(1029.98, cart.totalPrice, 0.01)
}实现方法,然后重构测试代码和生产代码。
totalPriceLayer-Specific Testing Summary
分层测试总结
Unit Tests (Domain & ViewModel)
单元测试(领域层 & ViewModel)
kotlin
class ScoreTest {
@Test
fun increment_increasesCurrentScore() {
val score = Score()
score.increment()
assertEquals(1, score.current)
}
}- Mock all dependencies with Mockito
- Test one behavior per test
- No Android framework dependencies
kotlin
class ScoreTest {
@Test
fun increment_increasesCurrentScore() {
val score = Score()
score.increment()
assertEquals(1, score.current)
}
}- 使用Mockito模拟所有依赖
- 每个测试验证一个行为
- 不依赖Android框架
Integration Tests (Repository + Database)
集成测试(仓库层 + 数据库)
kotlin
@RunWith(AndroidJUnit4::class)
class WishlistDaoTest {
private lateinit var db: AppDatabase
@Before
fun setup() {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).build()
}
@After
fun teardown() { db.close() }
}kotlin
@RunWith(AndroidJUnit4::class)
class WishlistDaoTest {
private lateinit var db: AppDatabase
@Before
fun setup() {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).build()
}
@After
fun teardown() { db.close() }
}Network Tests (API Layer)
网络测试(API层)
kotlin
class ApiServiceTest {
private val mockWebServer = MockWebServer()
@Test
fun fetchData_returnsExpectedResponse() {
mockWebServer.enqueue(
MockResponse().setBody("""{"id":1,"name":"Test"}""").setResponseCode(200)
)
val response = service.fetchData().execute()
assertEquals("Test", response.body()?.name)
}
}kotlin
class ApiServiceTest {
private val mockWebServer = MockWebServer()
@Test
fun fetchData_returnsExpectedResponse() {
mockWebServer.enqueue(
MockResponse().setBody("""{"id":1,"name":"Test"}""").setResponseCode(200)
)
val response = service.fetchData().execute()
assertEquals("Test", response.body()?.name)
}
}UI Tests (Espresso / Compose)
UI测试(Espresso / Compose)
kotlin
@Test
fun clickSaveButton_showsConfirmation() {
onView(withId(R.id.saveButton)).perform(click())
onView(withText("Saved!")).check(matches(isDisplayed()))
}kotlin
@Test
fun clickSaveButton_showsConfirmation() {
onView(withId(R.id.saveButton)).perform(click())
onView(withText("Saved!")).check(matches(isDisplayed()))
}Essential Test Dependencies
必备测试依赖
groovy
dependencies {
// Unit
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
// Integration & UI
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.arch.core:core-testing:2.2.0'
// Room & Network
testImplementation 'androidx.room:room-testing:2.6.1'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
}groovy
dependencies {
// 单元测试
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
// 集成测试 & UI测试
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.arch.core:core-testing:2.2.0'
// Room & 网络测试
testImplementation 'androidx.room:room-testing:2.6.1'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
}Test Naming Convention
测试命名规范
Use descriptive names following:
methodUnderTest_condition_expectedResultkotlin
fun addItem_emptyCart_cartHasOneItem()
fun calculateTotal_multipleItems_returnsSumOfPrices()
fun login_invalidCredentials_returnsError()
fun fetchUsers_networkError_showsErrorState()使用以下格式的描述性名称:
被测方法_测试条件_预期结果kotlin
fun addItem_emptyCart_cartHasOneItem()
fun calculateTotal_multipleItems_returnsSumOfPrices()
fun login_invalidCredentials_returnsError()
fun fetchUsers_networkError_showsErrorState()Patterns & Anti-Patterns
最佳实践与反模式
DO
应该
- Write tests first (always Red before Green)
- Keep tests small and focused (one assertion per concept)
- Use descriptive test names that document behavior
- Use test data factories for complex objects
- Test edge cases and error conditions
- Refactor tests alongside production code
- 先编写测试(始终遵循先红后绿)
- 保持测试小巧且聚焦(每个测试验证一个核心逻辑)
- 使用描述性的测试名称来记录功能行为
- 为复杂对象使用测试数据工厂
- 测试边缘情况和错误场景
- 同步重构测试代码和生产代码
DON'T
不应该
- Test implementation details (test behavior, not internals)
- Write tests for generated code (Hilt, Room DAOs)
- Test third-party libraries (Retrofit, Gson)
- Chase 100% coverage at expense of test quality
- Write slow, flaky, or order-dependent tests
- Skip the Red phase (you won't catch false positives)
- 测试实现细节(测试行为,而非内部逻辑)
- 为生成代码编写测试(Hilt、Room DAOs)
- 测试第三方库(Retrofit、Gson)
- 为了追求100%覆盖率而牺牲测试质量
- 编写缓慢、不稳定或依赖执行顺序的测试
- 跳过红阶段(无法发现假阳性结果)
Integration with Other Skills
与其他技能的集成
feature-planning → Define specs & acceptance criteria
↓
android-tdd → Write tests first, then implement (THIS SKILL)
↓
android-development → Follow architecture & Kotlin standards
↓
ai-error-handling → Validate AI-generated implementations
↓
vibe-security-skill → Security reviewKey Integrations:
- android-development: Follow MVVM + Clean Architecture for testable design
- feature-planning: Use acceptance criteria as test scenarios
- ai-error-handling: Validate AI output against test expectations
- superpowers:test-driven-development: General TDD workflow orchestration
feature-planning → 定义需求规格与验收标准
↓
android-tdd → 先编写测试,再实现功能(当前技能)
↓
android-development → 遵循架构与Kotlin标准
↓
ai-error-handling → 验证AI生成的实现
↓
vibe-security-skill → 安全评审关键集成点:
- android-development: 遵循MVVM + 清洁架构以实现可测试性设计
- feature-planning: 将验收标准作为测试场景
- ai-error-handling: 根据测试预期验证AI输出
- superpowers:test-driven-development: 通用TDD工作流编排
CI Pipeline
CI流水线
yaml
name: Android TDD
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Unit Tests
run: ./gradlew test
- name: Instrumented Tests
run: ./gradlew connectedAndroidTest
- name: Coverage Report
run: ./gradlew jacocoTestReportCI Rules:
- All tests must pass before merge
- Coverage reports generated on every PR
- Unit tests and instrumented tests run in parallel
yaml
name: Android TDD
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Unit Tests
run: ./gradlew test
- name: Instrumented Tests
run: ./gradlew connectedAndroidTest
- name: Coverage Report
run: ./gradlew jacocoTestReportCI规则:
- 所有测试必须通过才能合并代码
- 每个PR都要生成覆盖率报告
- 单元测试和仪器化测试并行运行
References
参考资料
- Google Testing Guide: developer.android.com/training/testing
- Mockito Kotlin: github.com/mockito/mockito-kotlin
- Espresso: developer.android.com/training/testing/espresso
- Architecture Samples: github.com/android/architecture-samples
- Google测试指南: developer.android.com/training/testing
- Mockito Kotlin: github.com/mockito/mockito-kotlin
- Espresso: developer.android.com/training/testing/espresso
- 架构示例: github.com/android/architecture-samples ",