锁定老帖子 主题:尝试了一下把TDD用到真正的项目中
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-09-29
最后修改:2009-09-29
总体上,搞了一段时间之后,我觉得代码质量比较高(Bug率较低),但效率很难让人满意,并且我也并不那么快乐。 1.写单元测试几乎成了一种负担。 a. 手动生成测试类太辛苦太无聊。不过后来发现了Fast Code Plugin,情况有所改观 b. 写一个代码立即写一个测试类,不符合批量作业的原理:你的头脑要不停地从开发者到测试者两者之间切换,效率低下,而且很让人沮丧;当重构发生时,单元测试也要重构,这更加让人不胜奇烦。后来我想的办法是先把一定的代码全部写好之后写测试来测,为了不遗漏,我会在写代码时做个标记(比如throw UnsupportedOperationException),然后在测试时按这些标记找到应测类。 c.单元测试不是那么好写。我的经验发现单元测试的代码量往往会超过被测代码。比如说,EasyMock的三步曲还是不够简洁,用JAVA写测试数据也挺麻烦。最终我发现,单元测试使开发周期加倍。 2.TDD提倡的自底向上的设计思路也会搞得很低效。 自底向上的设计,即先写代码再重构,的确可以让你的脑子免于很多思考,先把车开到山前,到了山前再开路。但我的体会就是“山前开路”发生的太频繁了,而且“返工”太多了。9点开好的一条临时小径,11点就得被重构掉,加上它的测试用例,再考虑到CVS等因素,反复重构非常低效。 所以我后来觉得,还是先设计为好。 不过,虽然有上面那些问题,但我觉得TDD仍是一条应该坚持走下去的路。虽然首次开发时时间较短,但由于Test Suite的存在,日后的维护代价会轻很多。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-09-29
最后修改:2009-09-29
呵呵,其实你提的abc三个问题你自己已经解决了前两个了,
至于第三个问题,建议试试http://code.google.com/p/mockito/ ,比EasyMock更easy。 另外你说“当重构发生时,单元测试也要重构,这更加让人不胜奇烦”, 如果只是代码的refactory,测试代码也可以一起被自动的重构掉啊, 如果是设计上的重构,那修改测试是难免的了,不过也有解决之道,就是把单元测试写得尽量“黑盒”:只断言输入输出,不断言行为。 |
|
返回顶楼 | |
发表时间:2009-09-29
ok. 我看看mockito好不好用。 我之所以坚持EasyMock,是因为我要用PowerMock来mock静态方法,而PowerMock又必须基于EasyMock.
至于单元测试的重构问题,我指的是修改类名。修改了被测类的类名,测试类的类名不会被自动改掉。 |
|
返回顶楼 | |
发表时间:2009-09-29
chenjianjx 写道 2.TDD提倡的自底向上的设计思路也会搞得很低效。
求这句话的出处。 我在尝试着自顶向下,似乎没问题,而且自顶向下是直接从需求入手,好像更自然。 |
|
返回顶楼 | |
发表时间:2009-09-29
chenjianjx 写道 这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。
这不是不严格的问题,你搞的根本不是TDD,充其量维护了一套自动测试用例。 既然你搞的不是TDD,就不要说TDD如何如何。 |
|
返回顶楼 | |
发表时间:2009-09-29
daquan198163 写道 如果是设计上的重构,那修改测试是难免的了,不过也有解决之道,就是把单元测试写得尽量“黑盒”:只断言输入输出,不断言行为。 刚开始实践TDD,有点疑问: 如果测试写得尽量“黑盒”,好像就不太好驱动出开发了 比如这样一个业务逻辑: 客户是VIP的话,打7折,否则不打折 我写的junit是: testCalculateForVIP() { price = calculate(vip, 10); assert price == 7; } 最终写的代码是: service: calculate(user, price) { some code .... discount = userDao.isVIP(user) ? 7 : 10; some code .... } 可让我疑惑的是,这个userDao.isVIP是怎么驱动出来的? |
|
返回顶楼 | |
发表时间:2009-09-29
所有代码都是受测试用例驱动而产生的,而测试用例之间只有功能上的逻辑联系,没有设计上的联系。
某种设计模式的引入,都是在发生冗余代码后才“不得以而为之” yuan 写道 chenjianjx 写道 2.TDD提倡的自底向上的设计思路也会搞得很低效。
求这句话的出处。 我在尝试着自顶向下,似乎没问题,而且自顶向下是直接从需求入手,好像更自然。 |
|
返回顶楼 | |
发表时间:2009-09-29
最后修改:2009-09-29
好吧,我不是 测试类驱动开发,而是 客户类驱动开发。
但问题是,如果用严格的TDD,我说的问题仍会存在。 tuti 写道 chenjianjx 写道 这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。
这不是不严格的问题,你搞的根本不是TDD,充其量维护了一套自动测试用例。 既然你搞的不是TDD,就不要说TDD如何如何。 |
|
返回顶楼 | |
发表时间:2009-09-29
wsgwz_2000 写道 daquan198163 写道 如果是设计上的重构,那修改测试是难免的了,不过也有解决之道,就是把单元测试写得尽量“黑盒”:只断言输入输出,不断言行为。 刚开始实践TDD,有点疑问: 如果测试写得尽量“黑盒”,好像就不太好驱动出开发了 比如这样一个业务逻辑: 客户是VIP的话,打7折,否则不打折 我写的junit是: testCalculateForVIP() { price = calculate(vip, 10); assert price == 7; } 最终写的代码是: service: calculate(user, price) { some code .... discount = userDao.isVIP(user) ? 7 : 10; some code .... } 可让我疑惑的是,这个userDao.isVIP是怎么驱动出来的? 实现calculate时,根据直觉、设计模式、职责单一等等这些原则、思想,就可以驱动出userDao.isVIP 不必追求用测试驱动出所有接口、设计, TDD的本质是从调用者、客户的角度思考设计,既然calculator就是userDao的调用者,那么它驱动出的设计应该跟TDD效果是一样。的。 |
|
返回顶楼 | |
发表时间:2009-09-29
最后修改:2009-09-29
tuti 写道 chenjianjx 写道 这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。
这不是不严格的问题,你搞的根本不是TDD,充其量维护了一套自动测试用例。 既然你搞的不是TDD,就不要说TDD如何如何。 我认为TDD这种方法之所以有效,本质在于: 它使得我们在开发过程中能不断的从测试用例获得反馈,然后在这些反馈的基础上去调整设计,自然而然的做出简单、合理的设计; 这里的核心是快速反馈,只要我写下一段代码能尽快的被客户(单元测试)使用、运行,并得到客户的反馈(好不好用、正不正确……),我就可以以此为依据来完善设计。 至于这些测试是在代码之前先写出来的还是在之后几分钟写出来的,没有什么大不了的,根据个人喜好、感觉走就行了,比如: 如果写代码时思路很乱,那就先写个测试,从外部思考,思考这个组件从外面开起来是什么样的,想清楚了以后再去想内部实现;然后完善测试、完善实现,如此反复。 反过来,如果我一开始就对实现比较清楚,有很多想法,那就直奔主题去用代码实现这些想法,写出一个最简单的实现后,立刻写个测试来验证,如此反复。 |
|
返回顶楼 | |