JUnit4使用教程.docx
- 文档编号:8691932
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:14
- 大小:31.69KB
JUnit4使用教程.docx
《JUnit4使用教程.docx》由会员分享,可在线阅读,更多相关《JUnit4使用教程.docx(14页珍藏版)》请在冰豆网上搜索。
JUnit4使用教程
JUnit4使用教程
本文主要通过一个实例介绍了如何使用JUnit4提供的各种功能开展有效的单元测试。
JUnit是Java社区中知名度最高的单元测试工具。
它诞生于1997年,由ErichGamma和KentBeck共同开发完成。
其中ErichGamma是经典著作《设计模式:
可复用面向对象软件的基础》一书的作者之一,并在Eclipse中有很大的贡献;KentBeck则是一位极限编程(XP)方面的专家和先驱。
麻雀虽小,五脏俱全。
JUnit设计的非常小巧,但是功能却非常强大。
MartinFowler如此评价JUnit:
在软件开发领域,从来就没有如此少的代码起到了如此重要的作用。
它大大简化了开发人员执行单元测试的难度,特别是JUnit4使用Java5中的注解(annotation)使测试变得更加简单。
在单元测试前首先规划单元测试代码应放在什么地方。
把它和被测试代码混在一起,这显然会照成混乱,因为单元测试代码是不会出现在最终产品中的。
建议分别为单元测试代码与被测试代码创建单独的目录,并保证测试代码和被测试代码使用相同的包名。
这样既保证了代码的分离,同时还保证了查找的方便。
下面的例子来自开发实践:
工具类WordDealUtil中的静态方法wordFormat4DB是专用于处理Java对象名称向数据库表名转换的方法(可以在代码注释中可以得到更多详细的内容)。
下面是第一次编码完成后大致情形:
packagecooljunit;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
/**
*对名称、地址等字符串格式的内容进行格式检查或者格式化的工具类
*/
publicclassWordDealUtil{
/**
*将Java对象名称(每个单词的头字母大写)按照数据库命名的习惯进行格式化
*格式化后的数据为小写字母,并且使用下划线分割命名单词
*例如:
employeeInfo经过格式化之后变为employee_info
*@paramnameJava对象名称
*/
publicstaticStringwordFormat4DB(Stringname){
Patternp=Ppile("[A-Z]");
Matcherm=p.matcher(name);
StringBuffersb=newStringBuffer();
while(m.find()){
m.appendReplacement(sb,"_"+m.group());
}
returnm.appendTail(sb).toString().toLowerCase();
}
}
它是否能按照预期的效果执行呢?
尝试为它编写JUnit单元测试代码如下:
packagecooljunit;
importstaticorg.junit.Assert.assertEquals;
importorg.junit.Test;
importorg.junit.runner.JUnitCore;
publicclassTestWordDealUtil{
//测试wordFormat4DB正常运行的情况
@Test
publicvoidwordFormat4DBNormal(){
Stringtarget="employeeInfo";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info",result);//System.out.println(“employee_info”.equals(result));
}
publicstaticvoidmain(String[]args){
JUnitCore().main(newString[]{“TestWordDealUtil”});
}
}
测试类TestWordDealUtil之所以使用“Test”开头,完全是为了更好的区分测试类与被测试类。
测试方法wordFormat4DBNormal调用执行被测试方法WordDealUtil.wordFormat4DB,以判断运行结果是否达到设计预期的效果。
需要注意的是,测试方法wordFormat4DBNormal需要按照一定的规范书写:
1.测试方法必须使用注解org.junit.Test修饰。
2.测试方法必须使用publicvoid修饰,而且不能带有任何参数。
测试方法中要处理的字符串为“employeeInfo”,按照设计目的,处理后的结果应该为“employee_info”。
assertEquals是由JUnit提供的一系列判断测试结果是否正确的静态断言方法(位于类org.junit.Assert中)之一,使用它将执行结果result和预期值“employee_info”进行比较,来判断测试是否成功。
下面简单介绍一下静态类org.junit.Assert。
该类主要包含以下22个方法:
1.assertEquals(),8个重载,用来查看对象中存的值是否是期待的值,与字符串比较中使用的equals()方法类似;
2.assertFalse()和assertTrue(),各2个重载,用来查看变量是是否为false或true,如果assertFalse()查看的变量的值是false则测试成功,如果是true则失败,assertTrue()与之相反;
3.assertSame()和assertNotSame(),各2个重载,用来比较两个对象的引用是否相等和不相等,类似于通过“==”和“!
=”比较两个对象;
4.assertNull()和assertNotNull(),各2个重载,用来查看对象是否为空和不为空;
5.fail(),2个重载,意为失败,用来抛出AssertionError错误。
有两个用途:
首先是在测试驱动开发中,由于测试用例都是在被测试的类之前编写,而写成时又不清楚其正确与否,此时就可以使用fail方法抛出错误进行模拟;其次是抛出意外的错误,比如要测试的内容是从数据库中读取的数据是否正确,而导致错误的原因却是数据库连接失败。
单元测试的范围要全面,比如对边界值、正常值、错误值得测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。
显然,以上测试才刚刚开始,需继续补充一些对特殊情况的测试:
publicclassTestWordDealUtil{
……
//测试null时的处理情况
@Test
publicvoidwordFormat4DBNull(){
Stringtarget=null;
Stringresult=WordDealUtil.wordFormat4DB(target);
assertNull(result);
}
//测试空字符串的处理情况
@Test
publicvoidwordFormat4DBEmpty(){
Stringtarget="";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("",result);
}
//测试当首字母大写时的情况
@Test
publicvoidwordFormat4DBegin(){
Stringtarget="EmployeeInfo";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info",result);
}
//测试当尾字母为大写时的情况
@Test
publicvoidwordFormat4DBEnd(){
Stringtarget="employeeInfoA";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info_a",result);
}
//测试多个相连字母大写时的情况
@Test
publicvoidwordFormat4DBTogether(){
Stringtarget="employeeAInfo";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_a_info",result);
}
……
}
再次运行测试。
此时,JUnit提示有两个测试情况未通过测试——当首字母大写时得到的处理结果与预期的有偏差,造成测试失败(failure);而当测试对null的处理结果时,则直接抛出了异常——测试错误(error)。
显然,被测试代码中并没有对首字母大写和null这两种特殊情况进行处理,修改如下:
//修改后的方法wordFormat4DB
/**
*将Java对象名称(每个单词的头字母大写)按照数据库命名的习惯进行格式化
*格式化后的数据为小写字母,并且使用下划线分割命名单词
*如果参数name为null,则返回null
*例如:
employeeInfo经过格式化之后变为employee_info
*@paramnameJava对象名称
*/
publicstaticStringwordFormat4DB(Stringname){
if(name==null){
returnnull;
}
Patternp=Ppile("[A-Z]");
Matcherm=p.matcher(name);
StringBuffersb=newStringBuffer();
while(m.find()){
if(m.start()!
=0)
m.appendReplacement(sb,("_"+m.group()).toLowerCase());
}
returnm.appendTail(sb).toString().toLowerCase();
}
JUnit将测试失败的情况分为两种:
failure和error。
Failure一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;而error则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),也可能是被测试代码中的一个隐藏的bug。
再次运行测试。
通过对WordDealUtil.wordFormat4DB比较全面的单元测试,现在的代码已经比较稳定,可以作为API的一部分提供给其它模块使用了。
当然,JUnit提供的功能决不仅仅如此简单,在接下来的内容中,会看到JUnit中很多有用的特性,掌握它们对灵活的编写单元测试代码非常有帮助。
Fixture
何谓Fixture?
它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。
JUnit专门提供了设置公共Fixture的方法,同一测试类中的所有测试方法都可以共用它来初始化Fixture和注销Fixture。
和编写JUnit测试方法一样,公共Fixture的设置也很简单,只需要:
1.使用注解org.junit.Before修饰用于初始化Fixture的方法。
2.使用注解org.junit.After修饰用于注销Fixture的方法。
3.保证这两种方法都使用publicvoid修饰,而且不能带有任何参数。
遵循上面的三条原则,编写出的代码大体是这个样子:
//初始化Fixture方法
@Before
publicvoidinit(){……}
//注销Fixture方法
@After
publicvoiddestroy(){……}
这样,在每一个测试方法执行之前,JUnit会保证init方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit又会调用destroy方法注销测试环境。
注意是每一个测试方法的执行都会触发对公共Fixture的设置,也就是说使用注解Before或者After修饰的公共Fixture设置方法是方法级别的(图1)。
这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。
图1方法级别Fixture执行示意图
可是,这种Fixture设置方式还是引来了批评,因为它效率低下,特别是在设置Fixture非常耗时的情况下(例如设置数据库链接)。
而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个测试方法重新设置一次Fixture。
因此在JUnit4中引入了类级别的Fixture设置方法,编写规范如下:
1.使用注解org.junit.BeforeClass修饰用于初始化Fixture的方法。
2.使用注解org.junit.AfterClass修饰用于注销Fixture的方法。
3.保证这两种方法都使用publicstaticvoid修饰,而且不能带有任何参数。
类级别的Fixture仅会在测试类中所有测试方法执行之前执行初始化,并在全部测试方法测试完毕之后执行注销方法(图6)。
代码范本如下:
//类级别Fixture初始化方法
@BeforeClass
publicstaticvoiddbInit(){……}
//类级别Fixture注销方法
@AfterClass
publicstaticvoiddbClose(){……}
图2类级别Fixture执行示意图
运行以下例子,可以更深刻理解方法的执行顺序:
importorg.junit.After;
importorg.junit.AfterClass;
importorg.junit.Before;
importorg.junit.BeforeClass;
importorg.junit.Test;
importorg.junit.runner.JUnitCore;
/**
*1:
标注BeforeClass的方法最先被执行,且只执行一次
*2:
标注AfterClass的方法最后被执行,且只执行一次
*3:
所有标注After的方法,会在每一个Test方法执行完后被执行一遍
*Before则是在每一个Test方法执行之前被执行一遍
*4:
After和Before执行的顺序与这些被标注的方法在代码中的位置无关,只与Junit内部排序规则相关.
*5:
Ignore标注的方法将会被加入测试方法序列,但是不被执行.
*/
publicclassExample{
@BeforeClass
publicstaticvoidinit(){
System.out.println("publicstaticvoidinit...");
}
@BeforeClass
publicstaticvoidinit1(){
System.out.println("publicstaticvoidinit1...");
}
@AfterClass
publicstaticvoidrelease(){
System.out.println("publicstaticrelease...");
}
@Before
publicvoidbefore(){
System.out.println("before...");
}
@Before
publicvoidbefore1(){
System.out.println("before1...");
}
@Test
publicvoidexample(){
System.out.println("---------TestBegin--------------");
System.out.println("Hello...");
}
@After
publicvoidclose1(){
System.out.println("After,shouldclose1...");
}
@Test
publicvoidexample1(){
System.out.println("---------TestBegin--------------");
System.out.println("Hello1...");
}
@After
publicvoidclose(){
System.out.println("After,shouldclose...");
}
@Test
publicvoidexample2(){
System.out.println("---------TestBegin--------------");
System.out.println("Hello2...");
}
@After
publicvoidclose2(){
System.out.println("After,shouldclose2...");
}
@Ignore("Notretryyet")
publicvoidtestIndexOut(){
System.out.println("Exceptiontest...");
}
publicExample(){
System.out.println("Exampleconstructrunning...");
}
publicstaticvoidmain(String[]args){
JUnitCore().main(newString[]{“Example”});
}
}
异常以及时间测试
注解org.junit.Test中有两个非常有用的参数:
expected和timeout。
参数expected代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,则JUnit会认为这个测试没有通过。
这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。
举例来说,方法supportDBChecker用于检查用户使用的数据库版本是否在系统的支持的范围之内,如果用户使用了不被支持的数据库版本,则会抛出运行时异常UnsupportedDBVersionException。
测试方法supportDBChecker在数据库版本不支持时是否会抛出指定异常的单元测试方法大体如下:
@Test(expected=UnsupportedDBVersionException.class)
publicvoidunsupportedDBCheck(){
……
}
注解org.junit.Test的另一个参数timeout,指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,则JUnit认为测试失败。
这个参数对于性能测试有一定的帮助。
例如,如果解析一份自定义的XML文档花费了多于1秒的时间,就需要重新考虑XML结构的设计,那单元测试方法可以这样来写:
@Test(timeout=1000)
publicvoidselfXMLReader(){
……
}
忽略测试方法
JUnit提供注解org.junit.Ignore用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行。
例如下面的代码便表示由于没有了数据库链接,提示JUnit忽略测试方法unsupportedDBCheck:
@Ignore(“dbisdown”)
@Test(expected=UnsupportedDBVersionException.class)
publicvoidunsupportedDBCheck(){
……
}
但是一定要小心。
注解org.junit.Ignore只能用于暂时的忽略测试,如果需要永远忽略这些测试,一定要确认被测试代码不再需要这些测试方法,以免忽略必要的测试点。
测试运行器
测试运行器是又一个新概念,JUnit中所有的测试方法都是由它负责执行的。
JUnit为单元测试提供了默认的测试运行器,但JUnit并没有限制必须使用默认的运行器。
相反,不仅可以定制自己的运行器(所有的运行器都继承自org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。
指定方法也很简单,使用注解org.junit.runner.RunWith在测试类上显式的声明要使用的运行器即可:
@RunWith(CustomTestRunner.class)
publicclassTestWordDealUtil{
……
}
显而易见,如果测试类没有显式的声明使用哪一个测试运行器,JUnit会启动默认的测试运行器执行测试类(比如上面提及的单元测试代码)。
一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用JUnit提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制JUnit测试方式时,显式的声明测试运行器就必不可少了。
测试套件
在实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。
为了解决这个问题,JUnit提供了一种批量运行测试类的方法,叫做测试套件。
这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。
测试套件的写法非常简单,只需要遵循以下规则:
1.创建一个空类作为测试套件的入口。
2.使用注解org.junit.runner.RunWith和org.junit.runners.Suite.SuiteClasses修饰这个空类。
3.将org.junit.runners.Suite作为参数传入注解RunWith,以提示JUnit为此类使用套件运行器执行。
4.将需要放入此测试套件的测试类组成数组作为注解SuiteClasses的参数。
5.保证这个空类使用public修饰,而且存在公开的不带有任何参
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JUnit4 使用 教程