测试驱动开发(TDD)在海外组的实践总结
测试驱动开发(TDD,Test Driven Development)是什么?测试驱动开发是一套开发方法论,有经验的开发人员都会对自己的代码编写测试,而测试驱动试图将这一过程做到极致,“如果测试被证明是有价值的,那么,我们为什么不能更频繁的去做测试,如果将测试时间提前有益于提高应用质量,为什么不先做测试,再编写应用.” 测试驱动开发要求在编写某个功能之前先编写测试代码,然后编写使测试通过的代码,通过测试来推动整个开发的进行. 测试驱动开发最早由Kent Beck提出,他还出过一本书,叫测试驱动开发,感兴趣的同学,可以买(download)来看看. 本文主要介绍海外的一系列技术实践,不打算做TDD的科普,感兴趣的同学请自行google. 另外,既然是技术实践,就不会纠结在方法论上,所以,没有做过TDD,没有写过单元测试的同学无需过分担忧,你还是能看懂的. 实践一: TDD的核心是任务拆分.咦,不是写测试,是任务拆分. 是的,跟我合作过的同学,尤其是新人同学大概都有印象,我一般会要求(我自己也常常这么干)把要做的事情写在一张卡片上,写成多行,每行代表一件事. 每完成一件,划掉一行,或者在前面打钩.
A. 从业务出发.
B,面向架构细分任务.
C,考虑边界条件,像QA一样思考 D,不断细化你的任务列表, 任务列表是测试驱动开发的核心,继续谈一下测试实现过程中的考量. 1,测试即文档,可读性优先. 1)测试代码是像生产代码一样,需要持续维护的,因此,它必须写好. 由于测试代码并不会在高并发的环境下运行,因此不用过早地在意性能的问题,而是优先考虑测试代码的可读性. 在早期的基于JUnit的单元测试中,技术人员通常喜欢在方法名上做文章. 如以下几种形式都广为使用. public void test_email_can_not_duplicate() {...}
@Test
public void should_throw_error_when_email_has_already_been_used() {...}
后来,社区中有人提出了BDD的概念,而后,测试框架也在逐步演进,有了cucumber和JBehave,于是有了下面的写法 @Given('I have 10 dollars')
public void i_have_10_dollars() {...}
将对测试的描述以更为自然语言的方式表达,当你的测试量逐渐增加,到几千个的时候,有种写小说的感脚. 然而太啰嗦,我不太喜欢,我们在海外组使用的是mocha,支持上述几种风格的描述. describe('POST /patients',() => {
it('should respond 200 after creating patients',(done) => {...})
it('should respond 401 when create time is not set',(done) => {...})
...
})
写完就是一篇华丽丽的接口文档,后来我们还引入了supersamples,感兴趣的同学到这里看demo,碉堡了,有没有. 类似的工具很多,以前还用过http://concordion.org/,也是无比华丽. 2) 测试三段论Given/When/Then Given,前提条件,When,测试点或者说被测方法,Then,结果验证. 比如,我们要测试删除功能. 那么,前提就是你要有一条数据可删. // Given
db('accounts').insert({id: 'xxx',lastName: 'xxxx'})
// When
const result = await Account.deleteById(id)
// Then
assertTrue(result)
当你不知道从何开始写测试的时候,首先搞清楚你要测什么,它的前提是什么,如何验证被测试的函数执行成功了. 多说一句,经常写单元测试的人,尤其是通过TDD的方式,写出来的代码可读性和可维护性都不会差,单元测试鼓励你写出纯函数,写出简洁的代码. 3) 测试方法必须是幂等的,即可重复执行的,测试之间不能有依赖. 说实话,这其实很难,很多人半途而废,觉得测试越写越复杂,甚至互相影响,一个挂了,红掉一片,就是这条没坚持好. 比如,有一些集成测试,会往数据库里写数据,就像上面的例子,很重要的一点就是,测试完成要还原数据库. 感谢极限编程社区,现在做这些事已经不那么麻烦. 哦,应该说很容易. 以海外为例. 我们使用knex来简化对数据库的操作,与之相配合的,有一个叫knex-cleaner的工具,专门负责对数据库的操作进行重置,感兴趣的同学可以来海外观摩或者参考文档. 借助mocha的beforeEach和afterEach hook,有了下面的代码,很简单吧,测试代码就不需要关注数据库环境了,当做全新的即可. beforeEach(() => knexCleaner.clean(db)) afterEach(() => knexCleaner.clean(db))
但是,有同学可能会问,难道我做测试每次都要准备数据么,那多麻烦. 答案如下
4) 测试是演进的,需要持续重构. 某种程度上说,测试代码对代码质量的要求是高于生产代码的. 5) 代码评审(code review)时,先review测试. 好了,关于测试代码的注意事项,先写到这里. 单元测试会导致你的代码量double,这个成本值得么,我来告诉你,非常值得. 短期内,它强迫你关注在业务/架构/问题本身,而不是想入非非的”可扩展的设计”,即更聚焦. 长期来看,它会让你对代码重构更有信心,你可以更加放心的优化你的代码,替换第三方库到新版本,甚至某种程度上做底层架构改变(比如升级babel6). 杏树林研发 秦汉 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |