论坛首页 综合技术论坛

尝试了一下把TDD用到真正的项目中

浏览 17332 次
该帖已经被评为精华帖
作者 正文
   发表时间:2009-09-29   最后修改:2009-09-29
这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。


总体上,搞了一段时间之后,我觉得代码质量比较高(Bug率较低),但效率很难让人满意,并且我也并不那么快乐。

   1.写单元测试几乎成了一种负担。
        a. 手动生成测试类太辛苦太无聊。不过后来发现了Fast Code Plugin,情况有所改观
        b. 写一个代码立即写一个测试类,不符合批量作业的原理:你的头脑要不停地从开发者到测试者两者之间切换,效率低下,而且很让人沮丧;当重构发生时,单元测试也要重构,这更加让人不胜奇烦。后来我想的办法是先把一定的代码全部写好之后写测试来测,为了不遗漏,我会在写代码时做个标记(比如throw UnsupportedOperationException),然后在测试时按这些标记找到应测类。
        c.单元测试不是那么好写。我的经验发现单元测试的代码量往往会超过被测代码。比如说,EasyMock的三步曲还是不够简洁,用JAVA写测试数据也挺麻烦。最终我发现,单元测试使开发周期加倍。
  
   2.TDD提倡的自底向上的设计思路也会搞得很低效。 自底向上的设计,即先写代码再重构,的确可以让你的脑子免于很多思考,先把车开到山前,到了山前再开路。但我的体会就是“山前开路”发生的太频繁了,而且“返工”太多了。9点开好的一条临时小径,11点就得被重构掉,加上它的测试用例,再考虑到CVS等因素,反复重构非常低效。 所以我后来觉得,还是先设计为好。



不过,虽然有上面那些问题,但我觉得TDD仍是一条应该坚持走下去的路。虽然首次开发时时间较短,但由于Test Suite的存在,日后的维护代价会轻很多。
   发表时间:2009-09-29   最后修改:2009-09-29
呵呵,其实你提的abc三个问题你自己已经解决了前两个了,
至于第三个问题,建议试试http://code.google.com/p/mockito/ ,比EasyMock更easy。

另外你说“当重构发生时,单元测试也要重构,这更加让人不胜奇烦”,
如果只是代码的refactory,测试代码也可以一起被自动的重构掉啊,
如果是设计上的重构,那修改测试是难免的了,不过也有解决之道,就是把单元测试写得尽量“黑盒”:只断言输入输出,不断言行为。
0 请登录后投票
   发表时间:2009-09-29  
ok. 我看看mockito好不好用。 我之所以坚持EasyMock,是因为我要用PowerMock来mock静态方法,而PowerMock又必须基于EasyMock.

至于单元测试的重构问题,我指的是修改类名。修改了被测类的类名,测试类的类名不会被自动改掉。
0 请登录后投票
   发表时间:2009-09-29  
chenjianjx 写道
2.TDD提倡的自底向上的设计思路也会搞得很低效。

求这句话的出处。

我在尝试着自顶向下,似乎没问题,而且自顶向下是直接从需求入手,好像更自然。
0 请登录后投票
   发表时间:2009-09-29  
chenjianjx 写道
这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。


这不是不严格的问题,你搞的根本不是TDD,充其量维护了一套自动测试用例。
既然你搞的不是TDD,就不要说TDD如何如何。
3 请登录后投票
   发表时间: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是怎么驱动出来的?
0 请登录后投票
   发表时间:2009-09-29  
所有代码都是受测试用例驱动而产生的,而测试用例之间只有功能上的逻辑联系,没有设计上的联系。
某种设计模式的引入,都是在发生冗余代码后才“不得以而为之”



yuan 写道
chenjianjx 写道
2.TDD提倡的自底向上的设计思路也会搞得很低效。

求这句话的出处。

我在尝试着自顶向下,似乎没问题,而且自顶向下是直接从需求入手,好像更自然。

0 请登录后投票
   发表时间:2009-09-29   最后修改:2009-09-29
好吧,我不是 测试类驱动开发,而是 客户类驱动开发。
但问题是,如果用严格的TDD,我说的问题仍会存在。


tuti 写道
chenjianjx 写道
这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。


这不是不严格的问题,你搞的根本不是TDD,充其量维护了一套自动测试用例。
既然你搞的不是TDD,就不要说TDD如何如何。

0 请登录后投票
   发表时间: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效果是一样。的。
0 请登录后投票
   发表时间:2009-09-29   最后修改:2009-09-29
tuti 写道
chenjianjx 写道
这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。

这不是不严格的问题,你搞的根本不是TDD,充其量维护了一套自动测试用例。
既然你搞的不是TDD,就不要说TDD如何如何。

我认为TDD这种方法之所以有效,本质在于:
它使得我们在开发过程中能不断的从测试用例获得反馈,然后在这些反馈的基础上去调整设计,自然而然的做出简单、合理的设计;
这里的核心是快速反馈,只要我写下一段代码能尽快的被客户(单元测试)使用、运行,并得到客户的反馈(好不好用、正不正确……),我就可以以此为依据来完善设计。
至于这些测试是在代码之前先写出来的还是在之后几分钟写出来的,没有什么大不了的,根据个人喜好、感觉走就行了,比如:
如果写代码时思路很乱,那就先写个测试,从外部思考,思考这个组件从外面开起来是什么样的,想清楚了以后再去想内部实现;然后完善测试、完善实现,如此反复。
反过来,如果我一开始就对实现比较清楚,有很多想法,那就直奔主题去用代码实现这些想法,写出一个最简单的实现后,立刻写个测试来验证,如此反复。
5 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics