免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2571 | 回复: 0
打印 上一主题 下一主题

【探讨】通过实例再讨论TDD [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-02-28 17:06 |只看该作者 |倒序浏览
在《测试驱动开发》(Kent Beck)的附录B,Kent Beck用了两页纸的篇幅,演示了一次完全以测试驱动的方式,开发计算斐波纳契数列。

先简短的抄一下代码,再谈谈我的看法。

第一个测试与第一次的代码
  1. public void testFibonacci();
  2.     assertEquals(0,fib(0););;
  3. }

  4. int fib(int n);{
  5.     return 0;
  6. }
复制代码
第二个测试与第二次的代码
Java代码
  1. public void testFibonacci();   
  2.     assertEquals(0,fib(0););;   
  3.     assertEquals(1,fib(1););;   
  4. }   
  5.   
  6. int fib(int n);{   
  7.     if(n==0); return 0;   
  8.     return 1;   
  9. }  

  10. public void testFibonacci();
  11.     assertEquals(0,fib(0););;
  12.     assertEquals(1,fib(1););;
  13. }

  14. int fib(int n);{
  15.     if(n==0); return 0;
  16.     return 1;
  17. }
复制代码
对测试代码进行改进,使之更为通用
Java代码
  1. public void testFibonacci();{   
  2.     int cases[][]={{0,0},{1,1}};   
  3.     for(int i=0;i<cases.length;i++);{   
  4.         assertEquals(cases[i][1],fib(cases[i][0]););;   
  5. }  

  6. public void testFibonacci();{
  7.     int cases[][]={{0,0},{1,1}};
  8.     for(int i=0;i<cases.length;i++);{
  9.         assertEquals(cases[i][1],fib(cases[i][0]););;
  10. }
复制代码
再增加n=2的测试
Java代码
  1. public void testFibonacci();{   
  2.     int cases[][]={{0,0},{1,1},{2,1}};   
  3.     for(int i=0;i<cases.length;i++);{   
  4.         assertEquals(cases[i][1],fib(cases[i][0]););;   
  5. }  

  6. public void testFibonacci();{
  7.     int cases[][]={{0,0},{1,1},{2,1}};
  8.     for(int i=0;i<cases.length;i++);{
  9.         assertEquals(cases[i][1],fib(cases[i][0]););;
  10. }
复制代码
不需要修改代码,测试就通过了。

再增加n=3的测试
Java代码
  1. public void testFibonacci();{   
  2.     int cases[][]={{0,0},{1,1},{2,1},{3,2}};   
  3.     for(int i=0;i<cases.length;i++);{   
  4.         assertEquals(cases[i][1],fib(cases[i][0]););;   
  5. }  

  6. public void testFibonacci();{
  7.     int cases[][]={{0,0},{1,1},{2,1},{3,2}};
  8.     for(int i=0;i<cases.length;i++);{
  9.         assertEquals(cases[i][1],fib(cases[i][0]););;
  10. }
复制代码
测试失败,于是修改代码,还是如法炮制
Java代码
  1. int fib(int n);{   
  2.     if(n==0); return 0;   
  3.     if(n<=2); return 1;   
  4.     return 2;   
  5. }  

  6. int fib(int n);{
  7.     if(n==0); return 0;
  8.     if(n<=2); return 1;
  9.     return 2;
  10. }
复制代码
然后,最为神奇的部分在下面的四次修改:

1:
Java代码
  1. int fib(int n);{   
  2.     if(n==0); return 0;   
  3.     if(n<=2); return 1;   
  4.     return 1+1;//注意这里   
  5. }  

  6. int fib(int n);{
  7.     if(n==0); return 0;
  8.     if(n<=2); return 1;
  9.     return 1+1;//注意这里
  10. }
复制代码
2:
Java代码
  1. int fib(int n);{   
  2.     if(n==0); return 0;   
  3.     if(n<=2); return 1;   
  4.     return fib(n-1);+1;//注意这里   
  5. }  

  6. int fib(int n);{
  7.     if(n==0); return 0;
  8.     if(n<=2); return 1;
  9.     return fib(n-1);+1;//注意这里
  10. }
复制代码
3:
Java代码
  1. int fib(int n);{   
  2.     if(n==0); return 0;   
  3.     if(n<=2); return 1;   
  4.     return fib(n-1);+fib(n-2);;//注意这里   
  5. }  

  6. int fib(int n);{
  7.     if(n==0); return 0;
  8.     if(n<=2); return 1;
  9.     return fib(n-1);+fib(n-2);;//注意这里
  10. }
复制代码
4:
Java代码
  1. int fib(int n);{   
  2.     if(n==0); return 0;   
  3.     if(n==1); return 1;//注意这里   
  4.     return fib(n-1);+fib(n-1);;   
  5. }  

  6. int fib(int n);{
  7.     if(n==0); return 0;
  8.     if(n==1); return 1;//注意这里
  9.     return fib(n-1);+fib(n-1);;
  10. }
复制代码
这是一个非常棒的过程。我们的讨论也从这里开始。

最后得到的这个函数,是一个递归函数,非常的简洁,但是往往会有效率问题。

(打住,告诉过你多少次了,不要考虑效率!)

不是我要考虑效率,只是这么简单的例子,要寻找别的设计方式,我只能从效率方面来说事。

OK,继续。假设我们要求9的斐波纳契数列的值,那么,fib函数就会去计算fib(8 )+fib(7)。然后我们再展开。
fib(9)=fib(8 )+fib(7)
fib(9)=(fib(7)+fib(6))+(fib(6)+fib(5))
注意,这里fib(6)就要被计算两遍。
fib(9)=((fib(6)+fib(5))+(fib(5)+fib(4)))+((fib(5)+fib(4))+(fib(4)+fib(3)))
注意,这里fib(5)要被计算3遍,fib(4)要被计算3遍。


理解我的意思了吗?这样的算法,存在严重的效率隐患。
如果我们要考虑效率,会如何写代码呢?
Java代码
  1. public int fib(int n);{   
  2.     int value0=0;   
  3.     int value1=0;   
  4.     int value=0;   
  5.     for(int i=0;i<=n;i++);{   
  6.         if(i==1);{   
  7.             value1=1;   
  8.             value=1;   
  9.         } else {   
  10.             value=value0+value1;   
  11.             value0=value1;   
  12.             value1=value;   
  13.         }   
  14.     }   
  15.     return value;   
  16. }  

  17. public int fib(int n);{
  18.     int value0=0;
  19.     int value1=0;
  20.     int value=0;
  21.     for(int i=0;i<=n;i++);{
  22.         if(i==1);{
  23.             value1=1;
  24.             value=1;
  25.         } else {
  26.             value=value0+value1;
  27.             value0=value1;
  28.             value1=value;
  29.         }
  30.     }
  31.     return value;
  32. }
复制代码
这个算法我就不解释了。有人也许会说,你这样不是TDD,你先写了程序!

不要紧,我可以假装先写了测试代码
Java代码
  1. public void testFibonacci();{   
  2.     int cases[][]={{0,0},{1,1},{2,1},{3,2}};   
  3.     for(int i=0;i<cases.length;i++);{   
  4.         assertEquals(cases[i][1],fib(cases[i][0]););;   
  5. }  

  6. public void testFibonacci();{
  7.     int cases[][]={{0,0},{1,1},{2,1},{3,2}};
  8.     for(int i=0;i<cases.length;i++);{
  9.         assertEquals(cases[i][1],fib(cases[i][0]););;
  10. }
复制代码
然后再把刚才的那个程序写出来,这样有什么问题吗?这样还算是TDD吗?

我仔细看了书了,Kent Beck说过“步伐”问题。我这样也可以算是TDD的,只是步子大了点。

那么我想说明什么问题呢?
1、无论先写测试还是先写代码,都需要考虑设计问题
2、在写测试之前考虑设计问题,不是什么罪过
3、考虑设计思路的深入与否,决定了步伐的大小
4、步伐太小的设计考虑,可能会陷入死角,无法再优化下去。从上面的代码可以看到,要想使递归算法变成循环算法,不是重构能够做到的。


最终的结论是:
代码就像你的左脚,测试就像你的右脚。
你可以先迈左脚,再迈右脚。然后一直走下去。
也可以先迈右脚,再迈左脚。然后一直走下去。
只要你不是一直单脚跳着前进,你都会走得很稳,而且没有人看得出区别来。


原文http://www.javaeye.com/topic/6551
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP