Test-Driven Development (TDD) — 是一种在代码实现之前编写测试的开发方法论。开发人员首先以失败的测试形式阐述预期行为,然后编写最少的代码使其通过,之后重构结果。根据 Martin Fowler (2023) 的说法,TDD 不是一种测试技术——它是一种设计技术,能够约束架构并减少代码编写阶段的缺陷数量。
要点
Test-Driven Development — 是一种软件开发实践,其中自动化测试决定生产代码的编写。与传统方法(先编写代码,然后测试)不同,TDD 颠倒了顺序:先编写测试,然后编写通过此测试的代码。
TDD 的奠基人是 Kent Beck,他在 20 世纪 90 年代末在 Extreme Programming (XP) 方法论的框架内阐述了这一实践。在 “Test-Driven Development: By Example” (2002) 一书中,Beck 描述了 TDD 的五条成为经典的规则:在生产代码之前编写测试,编写恰好足够通过测试的代码,以及在每个循环之后进行重构。
第一个原则 — 测试定义接口。开发人员在考虑组件如何实现之前被迫考虑组件将如何使用。这从一开始就形成了干净的 API。
第二个原则 — 最小化实现。当测试编写完成后,开发人员编写恰好足够通过测试的生产代码——不多写一行。这可以防止过早的抽象和过度复杂化,Martin Fowler 称之为 Speculative Generality。
TDD 与 “事后” 测试之间的关键区别 — 顺序的纪律。在 TDD 中,测试不仅检查代码——它还引导其结构。根据 Microsoft Research (Nagappan et al., 2008) 的研究,应用 TDD 的团队与传统方法的团队相比,缺陷密度降低了 40–90%。
Red-Green-Refactor 循环 — 是为每个新测试重复的三步序列。Red:编写一个不通过的测试。Green:编写最少的代码使测试通过。Refactor:在不改变其行为的情况下改进代码。
开发人员编写一个检查尚未实现功能的测试。在这个阶段,测试应该 失败——这确认了测试确实在检查某些东西。在 Android 开发环境中,JUnit 5 框架为失败的测试显示红色指示,这为该阶段命名。
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
在这个阶段,编写足够通过测试的最少生产代码。没有冗余——只有绿色指示所需的内容。如果实现可以是常量——那就让它成为常量。重构将在下一步进行,当新测试出现时。
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
绿色测试是 重构 的保障。开发人员可以重写实现、优化性能或改进可读性,确信测试将立即检测到与预期行为的任何偏差。在 Android 移动开发中,这个阶段对于提取公共接口和减少代码重复尤为重要。
在移动项目中应用 TDD 可带来可衡量的优势,这些优势得到了学术研究和领先开发工作室实践的证实。
IBM (Bhat & Nagappan, 2006) 对四个工业项目的研究表明,使用 TDD 的团队与类似传统工作的团队相比,缺陷减少了 40%。对于移动开发,在 Google Play 发布后修复错误的成本远高于代码编写阶段,这一指标至关重要。
按 TDD 编写的测试充当 API 的 活文档。进入项目的开发人员可以阅读测试并了解每个组件应该如何被使用。在团队高流动性的情况下——移动工作室的典型问题——这尤其有价值。
超过 90% 的测试覆盖代码使开发人员能够进行重构而不用担心破坏任何东西。Google 在 “Software Engineering at Google” (2020) 一书中将测试覆盖称为关键因素,它使得在拥有数百万行代码的项目中保持代码库整洁成为可能。
移动开发中的 TDD 生态系统包括用于单元测试、模拟和 UI 组件检查的工具——既适用于 Android,也适用于 iOS。
| 工具 | 平台 | 用途 |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | 单元测试的基础框架 |
| Mockito | Android | 创建模拟对象和验证调用 |
| MockK | Android (Kotlin) | 使用 Kotlin-first 语法的模拟,支持协程 |
| Turbine | Android | 测试 Kotlin Flow 和响应式流 |
| XCTest | iOS (Swift) | 标准测试框架 |
对于 Kotlin 上的 Android 项目,标准堆栈包括 JUnit 5 + MockK。MockK 优于 Mockito,因为它支持 Kotlin 的一级函数——密封类、协程和挂起函数,无需额外配置。
在 iOS 开发中,TDD 通过 XCTest 实现——Apple 的内置框架,提供断言、测试类以及通过 Xcode Server 或 GitHub Actions 与 CI/CD 的集成。iOS 上的模拟使用 Cuckoo 和 OHHTTPStubs 库。
让我们看看 Android 上 Kotlin 中一个真实的 TDD 场景——测试用户仓库。首先编写测试,然后——通过此测试的实现。
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val dao = mockk<UserDao>()
private val repo = UserRepository(api, dao)
fun `when api returns user then cache and emit`() = runTest {
val user = User(1, "Alice")
coEvery { api.getUser(1) } returns user
every { dao.insert(user) } returns Unit
val result = repo.getUser(1)
assertEquals(user, result)
verify { dao.insert(user) }
}
}
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUser(id: Int): User {
val user = api.getUser(id)
dao.insert(user)
return user
}
}
通过第一个测试后,我们添加第二个——检查 网络错误 时的行为。现在测试规定,当 API 失败时,仓库应从缓存返回数据。
fun `when api fails then return cached user`() = runTest {
val cached = User(1, "Cached Alice")
coEvery { api.getUser(1) } throws IOException()
every { dao.getById(1) } returns cached
val result = repo.getUser(1)
assertEquals(cached, result)
}
过渡到 TDD 伴随着典型错误,这些错误可能会使方法论的所有优势都付诸东流。理解这些陷阱有助于团队更有效地实施该实践。
第一个也是最常见的反模式 — 在一个测试中 测试过多的功能。测试应该只检查一个断言。如果测试失败,开发人员应该知道确切是哪里出了问题,而无需额外的调试。
第二个错误 — 编写一开始就通过的测试。如果测试从未至少变红一次,就没有信心它确实在检查任何东西。规则:永远不要相信你从未见过失败的测试。
第三个典型错误 — 停留在绿色阶段。重构 不是可选的,而是循环的强制阶段。没有它,代码库会退化,测试变得脆弱,TDD 的优势也会丧失。
常见问题
TDD 首先是一种设计技术,而不是测试技术。TDD 中的测试扮演规范的角色:它们在组件实现之前定义其 API。Kent Beck 本人称 TDD 为 “设计的纪律,而非测试的纪律”。
根据 Microsoft Research 的研究,团队需要 3 到 6 个月的持续练习才能让 TDD 成为习惯。前 2–3 周生产效率下降 15–30%,但在适应之后,由于调试时间的减少,会恢复到初始水平或超过它。
是的,但有局限性。对于 UI 逻辑(ViewModel、State),TDD 可以直接应用。对于可视化组件(Compose UI、SwiftUI Views),快照测试(snapshot testing)补充 TDD,但不能替代它。建议将业务逻辑和展示分离。
对于 遗留代码,建议使用 “表征测试”(characterization tests)策略——即测试编写在现有行为上,然后重构代码。这种方法在 Michael Feathers 的 “Working Effectively with Legacy Code” (2004) 一书中有所描述,允许逐步实施 TDD。
TDD 和 Clean Architecture 相互增强。干净架构要求层之间有清晰的边界,而 TDD 迫使开发人员通过测试来设计这些边界。领域层使用模拟依赖关系隔离测试,数据层——通过集成测试。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。