前言
译文:How TDD Changed the Way I Approach Softaware Development
本文根据上下文进行翻译,尽量通俗易懂
看之前,先了解几个词汇
TDD 测试驱动开发
QA 质量保证,其实这个岗位我也很模糊,有经验的可以说下,感激
SOLID 设计模式六大原则
作者自我介绍
我是一名有五年经验的软件工程师,这五年我有在大型开发团队也有在小型开发团队任职过,做过几个独立的项目,在开发这一块,我想分享一些我在用TDD开发之前和之后的经历。我曾经认为(开发流程是)"首先,解决问题,然后再编码",但是一年的 TDD 开发经历让我改变了想法,"先解决问题,再写测试",一个大型开发社区他们在实践 TDD 并希望我去调动开发者们(的兴趣)加入到我们,开始实践 TDD,学习一种写代码更整洁,更容易调试,更安全,测试驱动的新方法
我曾经如何编码
再实践 TDD 之前,我的大部分工作(流程)如下:(大多数开发团队也是这个流程)
- 团结接受到一份有效期内的门票列表
- 团队做了分配了任务,但没有任何的测试,也没有通过验证代码符合任务的验收标准,来执行开发人员的测试
- 拉取请求,审阅代码(code review)
- 合并公共代码,然后由测试者(QA)执行手动或自动化测试
- 修复一些审查过程中没有发现的bug,保证能走通一套业务流程(正常情况,一般还有故测试之类的)
- 将代码放到生产环境中(上线)
经验之谈,这些团队没有一个好的(开发流程),当团队围绕他们编写的代码没有一个编写良好的测试套件时,任务的平均生产时间会随着代码库的增加而不断增加。有一些原则,如 SOLID 设计,可以使代码更少耦合和更具凝聚力,但最终,没有一个好的测试套件,添加新产品功能和保持当前功能的复杂性将继续以微小的速度增长而且,在某个时刻,最终会使开发/发布过程变得非常缓慢
这就是软件开发中“慢速快”的说法失败,因为没有一个好的测试套件,随着代码库的代码增加,检查整个代码库中所有变化的影响变得非常困难和麻烦。QA 工程师可能会错过这些影响,并可能泄漏到生产环境中。
如果 QA 工程师确实抓住了开发人员错过的影响,平均修复时间会增加,并增加几轮测试。
TDD 如何解救我们
"Test-driven development(TDD)"是一个软件开发过程,依赖于一个短周期开发的重复:需求被转化为非常具体的测试用例,然后对软件进行改进,使测试通过
通过一个测试来说明,假设开发人员需要修补程序票,要将日期格式从MMM DD, YYYY HH: mm A 变成MMM DD, YYYY ,根据 moment.js 文档
开发人员通过函数formatShortDate获取格式化后的日期,下面是原代码:
export const formatShortDate = (value) => {
if(_.isEmpty(value))
return ''
return moment.utc(value).local().format('MMM DD, YYYY HH: mm A')
}
经过开发人员修改后,代码如下
export const formatShortDate = (value) => {
if(_.isEmpty(value))
return ''
return moment.utc(value).local().format('MMM DD, YYYY')
}
代码只修改了一行,所以工程师直接发布了代码到线上,并解决了这个问题,部署之后,用户反馈:说每日上午9点到下午6点计划的功能用不了,没有时间的日期,所以需要修复这个功能

如果有好的测试用例集成到部署的流程中,那么就会发现修复的程序导致了另一功能故障,并且会停止应用,在热修复部署之前,,可以先修复故障的功能。 上述功能的基础单元测试如下:
describe('formatShortDate', () => {
it('formats the date to format MMM DD, YYYY HH:mm A', () => {
const date = '2019-08-10 20:30:10' // database timestamp
const formattedDate = 'Aug 10, 2019 08:30 PM'
const returnedDate = formatShortDate(date)
expect(formattedDate).toEqual(returnedDate)
})
})
可以捕获损坏组件(我认为是 Vue 组件) 的测试用例可以是:
describe('UserScheduleWidget', () => {
let schedule = [
{
start_date: '2019-07-30T08:30:00',
end_date: '2019-07-30T10:30:00',
}
]
it('displays the schedule start and end date in correct format', () => {
const wrapper = Mount(UserScheduleWidget)
const spy = jest.spyOn(wrapper.vm, 'formatShortDate')
const formattedStartDate = wrapper.vm.formatShortDate(schedule.start_date)
const formattedEndDate = wrapper.vm.formatShortDate(schedule.end_date)
expect(spy).toHaveBeenCalled()
expect(formattedStartDate).toEqual('Jul 30, 2019 08:30 AM')
expect(formattedEndDate).toEqual('Jul 30, 2019 10:30 AM')
})
})
当开发人员将格式从"MMM DD、 YYY HH: mm A"更改为"MMM DD,YYYY"时,上述两个编写的测试用例都将失败,开发人员将知道更改会中断 UserStosWidget 组件,然后必要的纠正措施可以采取.
TDD的开发周期流程

- Red : 首先,写测试用例,并让测试失败,找到 bug
- Green : 编写足够多的代码让测试通过
- Refactor:对当前测试进行代码优化
之后,添加更多的测试以增加代码覆盖率并处理更多的案例,如果你的代码覆盖率超过了百分之九十,那么你的代码是经得起考验的
我学到了什么
TDD 帮我们减少了开发周期
你可以反驳说,如果我们编写的测试代码会比实现代码多,那么开发时间是如何缩短的。嗯,企业项目不是六个月的项目(短期项目),代码库随着新功能的加入越来越臃肿,并且随着功能的平均生产时间不断增加,每个功能的 Bug 百分比也不断增加。这是TDD帮助控制上述两个指标或几乎保持不变的地方。因此,TDD最初可能导致开发时间会比平常多 10-20% ,但在项目后期阶段,通常在一年之后,获得的好处有助于减少后续开发时间,并有助于保持项目代码复杂性低
TDD 使我成为更好的开发者
实现任何代码之前,你都会先写一个单元测试走一遍。单元测试在隔离中工作,并且模拟外部依赖项。给定一些输入,测试下的代码单元应生成一些已知的输出。如预期,它通过。否则,测试将失败。编写可测试单元代码的这一方面帮助我了解如何将大型组件分解为可测试代码的小单元,这是软件组合和分离的重要组成部分。如果要编写操作,则应该能够先对其进行单元测试,而无需呈现组件。如果要编写 UI 组件,则应该能够首先对其进行单元测试,而无需在浏览器中实际加载 UI 组件,并且 TDD 可帮助您实现此目的。因此,TDD可帮助您成为更好的开发人员
TDD 能帮助你更快的调试
当您编写一个测试来驱动业务实现的代码时,则测试仅在代码根据测试中的断言时通过。如果更改实现代码,则测试中的断言将失败。因此,通过了解哪些测试失败,您可以轻松地向下钻取到故障代码并向前推进修复程序。
如果没有TDD,调试就变得很麻烦,你必须花费大量的时间使用标准调试方法,比如在开发人员工具中放置断点或向代码添加控制台日志。因此,TDD 可帮助您轻松调试
结尾
TDD 改变了我写代码的方式。现在,我先考虑一下测试,它可以推动我正在处理的任务的实际实现。新的方式确实需要一段时间,但它可以编写可管理,可测试和更改失败的安全代码。如果你是一个开发人员还没有用过 TDD,我希望这篇文章激励你阅读更多关于它,尝试。如果你坚持一段时间,你也会看到你处理软件开发的方式上的差异