试驱动开发(TDD)是极限编程(XP)的重要特点,它是以持续性的测试来推动代码的开发,即可以简化代码,又可以保证质量。它改变了先编写代码,后编写测试,而是先编写测试,然后在编写代码来满足测试的方法。这样使得测试工作不仅仅是单纯的测试,而成为了设计的一部分。对于刚入门的编程者来说,也许觉得非常地别扭,但是当你习惯了这种编程方式之后,你会发现,TDD会成为你的得力助手。
下面的内容就以学习JUnit这个测试工具来简单的介绍TDD。(注:本文主要内容是介绍JUnit的使用,顺便抛砖引玉介绍TDD的开发过程。想了解更多关于TDD,可以参考其他的一些专业书籍)。
开发环境是:Eclipse3.2,已经集成了JUnit,所以就对Junit的配置不做介绍(网上这种文章太多了)。
下面的内容就以学习JUnit这个测试工具来简单的介绍TDD。(注:本文主要内容是介绍JUnit的使用,顺便抛砖引玉介绍TDD的开发过程。想了解更多关于TDD,可以参考其他的一些专业书籍)。
开发环境是:Eclipse3.2,已经集成了JUnit,所以就对Junit的配置不做介绍(网上这种文章太多了)。
第一章:JUnit快速入门:
需求:需要一个工具类,可以实现2个数的加、减、乘、除四个功能。我们给这个类起名叫CalculateUtil。
先编写测试代码:
- import junit.framework.TestCase; //1行
- public class CalculateUtilTest extends TestCase //2行
- {
- public void testCreate() //3行
- {
- new CalculateUtil();
- }
- }
代码解释:
1行:导入JUnit必须的类
2行:任何一个测试类必须集成TestCase类。
1行:导入JUnit必须的类
2行:任何一个测试类必须集成TestCase类。
测试类的类名命名 方式:被测试类类名+Test。这是一种比较好的习惯,
比如从CalculateUtilTest这个名称就知道被测试类是CalculateUtil
3行:测试类的测试方法。所有的测试方法都必须以test单词开头,并且方法的返
3行:测试类的测试方法。所有的测试方法都必须以test单词开头,并且方法的返
回类型为void。
编写完测试代码之后在Eclipse中运行该测试类,发现Junit运行出错(显示了一条红色杠)
编写完测试代码之后在Eclipse中运行该测试类,发现Junit运行出错(显示了一条红色杠)
这是在预料之中,因为我们还没有编写CalculateUtil类。为了使测试通过,那么下面开始编写CalculateUtil类。CalculateUtil类代码如下:
- public class CalculateUtil
- {
- }
然后再次运行测试类。这时会发现测试成功。
- import junit.framework.TestCase;
- public class CalculateUtilTest extends TestCase
- {
- public void testCreate() //1行
- {
- CalculateUtil ca=new CalculateUtil();
- assertEquals(5, ca.add(3,2)); //2行
- }
- }
在这里代码稍微改变了一下,使用ca引用来指向生成的CalculateUtil对象。
代码解释:
1行:测试类的方法必须以单词test开头
2行:assertEquals()方法。以assert单词开头的方法就是JUnit测试框架中的断
言方法。比如assertEquals(5, ca.add(3,2)); 方法就是断言ca的add(3,2)方
法返回的结果等于5。如果add(3,2)返回5那么测试成功,否则测试失败。
运行测试类,这时测试失败。那么向CalculateUtil中添加代码来保证测试成功:
为了验证add方法是否是真的返回两个数的相加的结果,我们继续在添加一行测试代码:
- public class CalculateUtil
- {
- public int add(int numOne,int numTwo)
- {
- return numOne+numTwo;
- }
- }
为了验证add方法是否是真的返回两个数的相加的结果,我们继续在添加一行测试代码:
- assertEquals(7, ca.add(4,2));
再运行会发现测试出错
故障跟踪中错误描述为:
junit.framework.AssertionFailedError:expected:<7> but was <6>
(如果你英语不是很差的话,相信这句话不用我解释也明白了)。在测试的时候,要选用一些容易出错的边界值进行测试,有正确的测试用例也要有错误的测试用例。
在完成add方法之后,下面开始进行除法的编写。首先编写测试代码:
代码解释:
1行:assertEquals("两个数相除", 2,ca.division(4, 2));和前面assertEquals方法
运行测试代码,测试通过。那么这样就可以了吗?由于除法非常特殊,如果除数为0那么会怎么样呢?我们继续在testDivision()中添加一行测试代码测试除数为0的情况:
运行测试,会发现testDivision()方法抛出了异常。
junit.framework.AssertionFailedError:expected:<7> but was <6>
(如果你英语不是很差的话,相信这句话不用我解释也明白了)。在测试的时候,要选用一些容易出错的边界值进行测试,有正确的测试用例也要有错误的测试用例。
在完成add方法之后,下面开始进行除法的编写。首先编写测试代码:
- import junit.framework.TestCase;
- public class CalculateUtilTest extends TestCase
- {
- public void testCreate()
- {
- CalculateUtil ca=new CalculateUtil();
- assertEquals(5, ca.add(3,2));
- }
- public void testDivision()
- {
- CalculateUtil ca=new CalculateUtil();
- assertEquals("两个数相除", 2,ca.division(4, 2)); //1行
- }
- }
代码解释:
1行:assertEquals("两个数相除", 2,ca.division(4, 2));和前面assertEquals方法
不一样,该方法多了一个参数,这个参数就是对该测试的一个简单的描述。
如果测试失败,那么会有提示。
为了使测试通过,就要在CalculateUtil类中添加一个division方法。代码如下:
- public class CalculateUtil
- {
- public int add(int numOne,int numTwo)
- {
- return numOne+numTwo;
- }
- public int division(int numOne,int numTwo)
- {
- return numOne/numTwo;
- }
- }
运行测试代码,测试通过。那么这样就可以了吗?由于除法非常特殊,如果除数为0那么会怎么样呢?我们继续在testDivision()中添加一行测试代码测试除数为0的情况:
- public void testDivision()
- {
- CalculateUtil ca=new CalculateUtil();
- assertEquals("两个数相除", 2,ca.division(4, 2));
- assertEquals("除数为0:", 2,ca.division(4,0));
- }
运行测试,会发现testDivision()方法抛出了异常。
测试表明:当除数为0的时,方法division()生成了一个异常。那么我们希望在测试用例中捕获该异常。更改后的testDivision()方法如下:
- public void testDivision()
- {
- CalculateUtil ca=new CalculateUtil();
- assertEquals("两个数相除:", 2,ca.division(4,2));
- try
- {
- assertEquals("除数为0:", 2,ca.division(4,0));
- fail("除数不为0"); //1行
- }
- catch(ArithmeticException ex){}
- }
代码解释:fail()一旦被执行,会立即中止测试,java虚拟机不再执行任何别的代码,并且会抛出junit.framework.AssertionFailedError异常。结合上面的try-catch语句,如果产生异常,那么会忽略fail()方法,如果没有产生异常那么就会调用fail()方法使测试失败,用这种方式就可以测试是否有异常产生,如果抛出异常,那么测试通过,否则测试失败。再次运行测试用例,一条绿杠显示测试通过。
但是这样还是不妥,因为类CalculateUtil毕竟是不知道测试类的存在,虽然在测试类中用异常捕获,但是一旦脱离测试类,那么division()方法的健壮性还是受到质疑。所以从新修改division()方法
再重新运行测试用例,测试通过。
注:处理异常的方式很多,这里只是其中一种做法。也可以在division()方法中写一段业务逻辑,判断numTwo是否为0,如果为0则抛出异常等……,在这里不做深入讨论。
从刚才的几个例子中,可以大概的知道TDD的开发思路,也知道JUnit的基本用法。但当我们反观CalculateUtilTest 类中的测试方法可以发现:类中的两个测试方法都构造了CalculateUtil的对象ca,但是对于程序来说,我们只需要构造一个对象即可。我们可以对CalculateUtilTest类中的代码稍作修改。修改后代码如下:
代码解释:
1行:JUnit在执行每个测试之前都先执行setUp方法,可以将公共的测试初始化代码放在setUp方法中。与setUp功能方法相反的是protected void tearDown() throws Exception方法;可以将测试结束后要执行的收尾工作放入tearDown方法中,比如:关闭数据库连接,关闭流……
但是这样还是不妥,因为类CalculateUtil毕竟是不知道测试类的存在,虽然在测试类中用异常捕获,但是一旦脱离测试类,那么division()方法的健壮性还是受到质疑。所以从新修改division()方法
- public int division(int numOne,int numTwo)throws ArithmeticException
- {
- return numOne/numTwo;
- }
再重新运行测试用例,测试通过。
注:处理异常的方式很多,这里只是其中一种做法。也可以在division()方法中写一段业务逻辑,判断numTwo是否为0,如果为0则抛出异常等……,在这里不做深入讨论。
从刚才的几个例子中,可以大概的知道TDD的开发思路,也知道JUnit的基本用法。但当我们反观CalculateUtilTest 类中的测试方法可以发现:类中的两个测试方法都构造了CalculateUtil的对象ca,但是对于程序来说,我们只需要构造一个对象即可。我们可以对CalculateUtilTest类中的代码稍作修改。修改后代码如下:
- public class CalculateUtilTest extends TestCase
- {
- private CalculateUtil ca;
- protected void setUp() throws Exception //1行
- {
- ca=new CalculateUtil();
- }
- public void testCreate()
- {
- assertEquals(5, ca.add(3,2));
- assertEquals(7, ca.add(4,2));
- }
- public void testDivision()
- {
- CalculateUtil ca=new CalculateUtil();
- assertEquals("两个数相除:", 2,ca.division(4,2));
- try
- {
- assertEquals("除数为0:", 2,ca.division(4,0));
- fail("除数不为0");
- }
- catch(ArithmeticException ex){}
- }
- }
代码解释:
1行:JUnit在执行每个测试之前都先执行setUp方法,可以将公共的测试初始化代码放在setUp方法中。与setUp功能方法相反的是protected void tearDown() throws Exception方法;可以将测试结束后要执行的收尾工作放入tearDown方法中,比如:关闭数据库连接,关闭流……