一个新手的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.开发过程中发现可能出现的错误可以及时加到测试里面,可以保证测试的可靠性。
浙公网安备 33010602011771号