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