一个新手的TDD实践

首先说一下我的情况,目前对C++有一定经验,对面向对象有一定了解,但是对Java还很不熟悉。

关于TDD,在《Clean Code》里看过相关章节,已经了解了TDD的三大原则,也理解其中小步前进的理念。

但是对怎样写一个Java的测试还不了解,于是开始Google。

 

学习总是从模仿开始,搜到了一篇测试驱动开发的实践,这篇博文是看《测试驱动开发艺术》后写的,而且里面有比较详细的可执行代码。

在我学习的初期提供了很大的帮助,这里遇到了 ${name} 这样的正则表达式,也遇到了 Map HashMap这些Java标准库。

还有Java相比与C++的新特性 for(entry:entrySet()) 这样的遍历方式。

很可惜iteye的博客只允许注册用户评论,不然我一定会在评论中致谢,顺便跟他说一下最后一段代码里那个evaluate应该是execute;

要建立测试,需要先安装Junit或者groovy等测试工具,而且好像是要在建立项目的时候就要选上支持该工具。

 

这样就基本最基本的东西,下面开始我要做的项目。

import org.junit.Test;
import static org.junit.Assert.*;

public class MarsMapTest {
    @Test
    public void OneMap(){
        MarsMap marsMap = new MarsMap("5 6");
        int expectedX = 5;
        int actualX = marsMap.maxX();
        int expectedY = 6;
        int actualY = marsMap.maxY();
        assertEquals(expectedX, actualX);
        assertEquals(expectedY, actualY);
    }


}

这里遇到了一些问题,首先这个类我原本是想命名为Map的,但是和Java的保留字冲突了,这样会导致一系列问题。

所以把类名做了一些调整,上一次我建立一个Note类也遇到过类似问题,这次就有一点经验了。

这个测试的想法是这样的,给这个类一个输入,通过空格隔开这样给这个地图一个二维坐标。

在项目初期暂时不考虑小数的坐标,这会导致相对复杂的判断和处理方式。

 

接下来就该写生产代码了

public class MarsMap {
    public MarsMap(String string){
    }

    public int maxX(){
        return 5;
    }

    public int maxY(){
        return 6;
    }
}

嗯,我使了点坏,这样说明测试是不充分的。

那么接下来,就应该修改我的测试了,我觉得应该是随机的两个整数都可以得到正确的结果。

而不是总返回5,6。那么我需要查找一下有关Java里面随机数相关资料,觉得写的不错,让我基本上看懂了。

然后还有把随机数变成字符串,这需要了解一下Java里处理整数转字符串的资料,这个看了几个搜索第一页的,结果都差不多。

 

接下来,我需要修改我的测试代码。

 1 import org.junit.Test;
 2 import static org.junit.Assert.*;
 3 
 4 public class MarsMapTest {
 5     @Test
 6     public void OneMap(){
 7         int x = (int) (Math.random()*10);
 8         int y = (int) (Math.random()*10);
 9         String string = ""+x+" "+y;
10         MarsMap marsMap = new MarsMap(string);
11         int actualX = marsMap.maxX();
12         int actualY = marsMap.maxY();
13         assertEquals(x, actualX);
14         assertEquals(y, actualY);
15     }
16 }

好的,这下再运行测试,测试通过不了了。

java.lang.AssertionError: 
Expected :8
Actual   :5

那么就应该开始修改生产代码了,现在要做的就是把字符串提取整型,以空格为间隔符。

 1 import static java.lang.Integer.*;
 2 
 3 public class MarsMap {
 4     private int maxX;
 5     private int maxY;
 6     public MarsMap(String string){
 7         String[] strarray = string.split(" ");
 8         maxX = parseInt(strarray[0]);
 9         maxY = parseInt(strarray[1]);
10     }
11 
12     public int maxX(){
13         return this.maxX;
14     }
15 
16     public int maxY(){
17         return this.maxY;
18     }
19 }

写生产代码的同时就不难发现,这样的生产代码对字符串中输入错误的情况没有处理。

比如输入的不是整型或者,输入的字符串没有空格,这时候就需要异常处理。

这里就涉及到了很多设计的问题,从结果导向的角度来看,褐鹤认为当输入错误的情况下,MarsMap类不应该被正常创建。

所以,应该是抛出一个由上层处理的异常,那么测试,只要收到一个异常就可以了。

 

 6 public class MarsMapTest {
 7     ...//前略
 8 
 9     public void OneStringWithNoSpace(){
10         int x = (int) (Math.random()*100);
11         String string = ""+x+"nospace";
12         inputExceptionTest(string);
13     }
14 
15     private void inputExceptionTest(String input){
16         try{
17             MarsMap marsMap = new MarsMap(input);
18         }catch (InputException inputEx){
19             assertTrue(true);
20         }
21     }
22 }

然后就会提示你找不到  InputException,接着就该修改生产代码了。

但是不难发现现在测试代码已经需要重构了,这个时候我的选择是先重构测试代码。

 7 public class MarsMapTest {
 8     MarsMap marsMap;
 9     int randomInt;
10     @Before
11     public void SetUp(){
12         randomInt = (int) (Math.random()*100);        
13     }
14     @Test
15     public void TwoIntWithOneSpace() throws Exception {
16         int x = randomInt;
17         int y = randomInt;
18         String string = ""+x+" "+y;
19         marsMap = new MarsMap(string);
20         int actualX,actualY;
21         actualX = marsMap.maxX();
22         actualY = marsMap.maxY();
23         assertEquals(x, actualX);
24         assertEquals(y, actualY);
25     }
26 
27     @Test
28     public void OneStringWithNoSpace(){
29         int x = (int) (Math.random()*100);
30         String string = ""+x+"nospace";
31         inputExceptionTest(string);
32     }
33 
34     private void inputExceptionTest(String input){
35         try{
36             marsMap = new MarsMap(input);
37         }catch (Exception inputEx){
38             assertTrue(true);
39         }
40     }
41 }

这段代码就自己也觉得写的不是很好,不过作为一个新手,水平就在这里暴露无疑了,如果有高手看到了还希望能多指点。

接下来就是对生产代码的改造。

 1 import static java.lang.Integer.*;
 2 
 3 public class MarsMap {
 4     private int maxX;
 5     private int maxY;
 6     public MarsMap(String string) throws Exception{
 7         try{
 8             String[] strarray = string.split(" ");
 9             if(strarray.length != 2) {
10                 throw new Exception();
11             }
12             maxX = parseInt(strarray[0]);
13             maxY = parseInt(strarray[1]);
14         }
15         catch (Exception e){
16             System.out.println("The Input should be two integer with one space between them.");
17         }
18     }
19 
20     public int maxX(){
21         return this.maxX;
22     }
23 
24     public int maxY(){
25         return this.maxY;
26     }
27 }

嗯,大概就是这样,通过这次实践,大概能够感觉到自己水平的不足,不过还是体会到测试驱动开发的好处:

1.测试覆盖率有保证;

2.开发过程中比较平滑,小步前进。

3.开发过程中发现可能出现的错误可以及时加到测试里面,可以保证测试的可靠性。

 

posted on 2012-12-07 16:05  褐鹤  阅读(477)  评论(0编辑  收藏  举报

导航