JUnit和单元测试入门简介之一
1 .单元测试概述
单元测试——是最小粒度的测试,以测试某个功能或代码块。一般由程序员来做,因为它需要知道内部程序设计和编码的细节。
1.1. 单元测试的好处
A、提高开发速度——测试是以自动化方式执行的,提升了测试代码的执行效率。
B、提高软件代码质量——它使用小版本发布至集成,便于实现人员除错。同时引入重 构概念,让代码更干净和富有弹性。
C、提升系统的可信赖度——它是回归测试的一种。支持修复或更正后的“再测试”,可确保代码的正确性。
1.2 单元测试的针对对象
A、面向过程的软件开发针对过程。
B、面向对象的软件开发针对对象。
C、可以做类测试,功能测试,接口测试(最常用于测试类中的方法)。
1.3 单元测试工具和框架
目前的最流行的单元测试工具是xUnit系列框架,常用的根据语言不同分为JUnit(java), CppUnit(C ),DUnit (Delphi ),NUnit(.net),PhpUnit(Php )等等。该测试框架的第一个和最杰出的应用就是由Erich Gamma (《设计模式》的作者)和Kent Beck(XP(Extreme Programming)的创始人 )提供的开放源代码的JUnit。
2. 什么是JUnit及其特性
如果您要对编写的程序进行测试,应该如何进行呢?传统的测试方式是通过信赖人工对输出结果的判断,缺少效率且通常难以组织,且针对单一程序通常要设计专门的测试程序,如果您是在编写Java,您可以使用JUnit来为你提供有效的测试。
2.1. 什么是JUnit?
在这里引述一下JUnit FAQ中的解释。
JUnit是一个开放原始码的Java测试框架(testing framwork),它用来编写与执行重复性的测试,它是用在单元测试框架的xUnit架构的贯例。
2.2. JUnit的好处
A、可以使测试代码与产品代码分开。
B、针对某一个类的测试代码通过较少的改动便可以应用于另一个类的测试。
C、易于集成到测试人员的构建过程中,JUnit和Ant的结合可以实施增量开发。
D、JUnit是公开源代码的,可以进行二次开发。
C、可以方便地对JUnit进行扩展。
2.3 JUnit单元测试编写原则:
A、是简化测试的编写,这种简化包括测试框架的学习和实际测试单元的编写。
B、是使测试单元保持持久性。
C、是可以利用既有的测试来编写相关的测试。
2.4 JUnit包括以下的特性:
A. 对预期结果的断言
B. 对相同共享资料的测试组装
C. 易于组织与执行测试的测试套件
D 图形与文字界面的测试器
3. JUnit 的使用入门
现在很多Java开发工具都集成了JUnit,如MyEclipse,Netbean,JBuilder等等,你可以直接使用。当然你也可以在JUnit的官方网站上下载,网址是http://junit.org
我自己用的是MyEclipse6.6版本的java开发工具,JUnit是4.5版本。现在我们来写一个java类,也就是被测试的类,然后用JUnit来进行单元测试。
首先,在MyEclipse中建一个java工程,我这里的工程名为javaproject2,引入JUnit4.5相关的jar文件,把环境搭建好。然后建立相应的包。这里应当注意,为了方便管理,我们的源代码和测试代码最好不要放在同一个代码文件夹中的同一个包中。我们可以再建一个代码文件夹test,并在其中建一个与源代码文件夹(src)中的包名一致的包。这样做的好处是源代码和测试代码虽然不在同一个文件夹,但它们的.class文件都在同一个文件夹中,实现了源代码和测试代码的分离,方便管理。
如图1.0所示:
(图1.0)
搭建好测试环境后,就可以进行相关类的编写了。这里我设计了一个税收类Revenue.java,放在src中的com.cuckoo2010包中。该类包含一个税费计算的方法revenuemethod(double mymoney),方法具体的实现逻辑为:当个人收小于或等于800则不征税,当个人收入大于800底于2000元时,征收百分之七的税,当个人收入大于2000且底于5000元时,征收百分之十五的税,当个人收入超过5000时,征收百分之二十五的税。设定一个异常状态,当输入值等于或小于零时抛出一个异常。代码如下:
package com.cuckoo2010;
/**
* 被测试的类 Revenue.java
* @author 松子煮茶
*/
public class Revenue {
private double money;
public double revenuemethod(double mymoney) throws Exception {
//个人收小于或等于800则不征税
if (mymoney <= 800) {
return this.money = mymoney;
}
//个人收入大于800底于2000元时,征收百分之七的税
else if (mymoney <= 2000) {
return this.money = mymoney - mymoney * 0.07;
}
//个人收入大于2000且底于5000元时,征收百分之十五的税
else if (mymoney > 2000 && mymoney <= 5000) {
return this.money = mymoney - mymoney * 0.15;
}
//个人收入超过5000时,征收百分之二十五的税
else if (mymoney > 5000) {
return this.money = mymoney - mymoney * 0.25;
}
else if (mymoney <= 0) {
/**
* 自定义异常
* 当金额小于或等于0时,系统抛出异常
*/
throw new Exception("金额不符合要求,必须大于零!");
}
return this.money;
}
}
写完这个类后,我们可以根据这个类设计一个测试用例,也就是各种输入值,预期值及实际值。
如表1.1所示:
表1.1
| 输入值 | 预期值 | 实际值 |
正常测试数据 | 1.0 | 1.0 | 1.0 |
500.0 | 500.0 | 500.0 | |
799.0 | 799.0 | 799.0 | |
1999.0 | 1859.07 | 1859.07 | |
2999.0 | 2549.15 | 2549.15 | |
4999.0 | 4249.15 | 4249.15 | |
5999.0 | 4499.25 | 4499.25 | |
边界测试数据 | 800.0 | 800.0 | 800.0 |
2000.0 | 1860.0 | 1860.0 | |
5000.0 | 4250.0 | 4250.0 | |
999999999.0 | 749999999.25 | 7.4999999925E8 | |
错误的测试数据 | 0.0 | 金额不符合要求,必须大于零! | 金额不符合要求,必须大于零! |
-1.0 | 金额不符合要求,必须大于零! | 金额不符合要求,必须大于零! |
接下来,再设计一个测试类,名为RevenueTest.java,放在test中的com.cuckoo2010包中。该类中包含一个testRevenueMethod()方法,并用JUnit中的断言语句执行表1.1中的数据,如果预期值等于实际值,则说明被测试的类能正常实现其功能,反之,则说明被测试的类有缺陷,需要找出缺陷并及时修改。RevenueTest.java的代码为:
package cum.cuckoo2010;
import com.cuckoo2010.Revenue;
import junit.framework.TestCase;
/**
* 建立测试Revenue类的测试类RevenueTest
* 采用断言语句进行测试
*
* @author 松子煮茶
*/
public class RevenueTest extends TestCase {
//创建Revenue类的对象
Revenue revenue = new Revenue();
//测试方法
public void testRevenueMethod() {
/**
* 正常的测试数据
*/
try {
assertEquals(1.0,revenue.revenuemethod(1.0));
assertEquals(500.0,revenue.revenuemethod(500.0));
assertEquals(799.0,revenue.revenuemethod(799.0));
assertEquals(1859.07,revenue.revenuemethod(1999.0));
assertEquals(2549.15,revenue.revenuemethod(2999.0));
assertEquals(4249.15,revenue.revenuemethod(4999.0));
assertEquals(4499.25,revenue.revenuemethod(5999.0));
} catch (Exception e) {
e.printStackTrace();
}
/**
* 边界值测试数据
*/
try {
assertEquals(800.0,revenue.revenuemethod(800.0));
assertEquals(1860.0,revenue.revenuemethod(2000.0));
assertEquals(4250.0,revenue.revenuemethod(5000.0));
assertEquals(7.49999999925E8,revenue.revenuemethod(999999999.9);
} catch (Exception e) {
e.printStackTrace();
}
/**
* 错误的测试数据
*/
try {
revenue.revenuemethod(0.0);
revenue.revenuemethod(-1.0);
} catch (Exception e) {
e.printStackTrace();
assertEquals("金额不符合要求,必须大于零!",e.getMessage());
}
}
}
运行这个测试类,我们可以看到,测试居然通过了。这显然是不符合我们最初的要求。因为,虽然正常的测试数据和边界测试数据虽然通过了测试,但执行错误的测试数据时并没有抛出我们自己定义的异常(金额不符合要求,必须大于零!),所以我们可以断定,源程序是有bug的。Bug在哪呢?再次检查一下源代码,我们发现原来是一个逻辑性错误。Bug就出在这里
if (mymoney <= 800) {
return this.money = mymoney;
}
应该改为:
if (mymoney > 0 && mymoney <= 800) {
return this.money = mymoney;
}
再次运行测试类时,就可以看到异常了(如图1.2和1.3),所以测试通过。
(图1.2)
(图1.3)
4. 小结
软件测试的目的之一就是尽早地发现软件中存在的错误,从而降低软件质量成本,所以从道理上说,单元测试是很重要的。按阶段进行测试是一种基本的测试策略,而单元测试是测试执行过程中的第一个阶段,同时,目前国内软件企业对于软件的重视程度有了很大提高,基于条件的限制,许多软件企业还无法开展全面测试,由于单元测试无须太多额外的人员和设备,目前已经被大多数年软件企业认知并实施。
所以,无论是对于开发人员,还是对于专业的测试人员,都应当了解单元测试,并掌握它。对于从事java开发人员或是测试人员,不防花点时间学学JUnit这个优秀的测试框架。它能提高你的测试效率,在一定程度上赢得了软件上市的时间。
今晚就到这吧,近期将会继续推出相关JUnit及单元测试的文章,敬请观注,同时还请大家多多指点,提出意见,共同提高。最后,谢谢大家的观注。祝大家晚安!