单元测试JUnit的应用.docx
- 文档编号:9297839
- 上传时间:2023-02-04
- 格式:DOCX
- 页数:15
- 大小:72.30KB
单元测试JUnit的应用.docx
《单元测试JUnit的应用.docx》由会员分享,可在线阅读,更多相关《单元测试JUnit的应用.docx(15页珍藏版)》请在冰豆网上搜索。
单元测试JUnit的应用
单元测试(JUnit)的应用
三.JUnit的实际应用例子
3.1功能需求
将Java对象名称(每个单词的头字母大写)按照数据库命名的习惯进行格式化,格式化后的数据为小写字母,并且使用下划线分割命名单词,要求对输入数据进行非法验证。
3.2编写测试
首先新建一个Java工程——TestJUnit。
打开项目TestJUnit的属性页->选择“JavaBuildPath”子选项->点选“AddLibrary…”按钮->在弹出的“AddLibrary”对话框中选择JUnit(图2),并在下一页中选择版本4.1后点击“Finish”按钮。
图2
请注意JDK的版本:
JUnit4.1是基于Java5的升级版本,它使用了Tiger中的很多新特性来简化原有的使用方式。
正因为如此,它并不能直接运行在JDK1.4.x版本上。
如果您需要在JDK1.4.x版本使用JUnit的话,请使用3.8.1版本。
我们在项目TestJUnit根目录下添加一个新目录testsrc,并把它加入到项目源代码目录中(加入方式见图3)以存放测试代码,使之与源代码分离。
图3
一切准备就绪,下面是编写的单元测试用例:
packagecom.meritit;
importstaticorg.junit.Assert.AssertNull;
importstaticorg.junit.Assert.AssertEquals;
importorg.junit.Test;
publicclassTestWordDealUtil{
//测试wordFormat4DB一般的处理情况
@TestpublicvoidwordFormat4DBNormal(){
Stringtarget="employeeInfo";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info",result);
}
//测试null时的处理情况
@TestpublicvoidwordFormat4DBNull(){
Stringtarget=null;
Stringresult=WordDealUtil.wordFormat4DB(target);
assertNull(result);
}
//测试空字符串时的处理情况
@TestpublicvoidwordFormat4DBEmpty(){
Stringtarget="";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("",result);
}
//测试当首字母大写时的情况
@TestpublicvoidwordFormat4DBegin(){
Stringtarget="EmployeeInfo";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info",result);
}
//测试当尾字母为大写时的情况
@TestpublicvoidwordFormat4DBEnd(){
Stringtarget="employeeInfoA";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info_a",result);
}
//测试多个相连字母大写时的情况
@TestpublicvoidwordFormat4DBTogether(){
Stringtarget="employeeAInfo";
Stringresult=WordDealUtil.wordFormat4DB(target);
assertEquals("employee_a_info",result);
}
}
3.3编写代码
按照需求所写的代码如下:
packagecom.meritit;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
/**
*对名称、地址等字符串格式的内容进行格式检查
*@authorlvxg
*/
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();
}
}
运行测试,结果如图4所示:
图4
JUnit运行界面提示我们有两个测试情况未通过——当首字母大写时得到的处理结果与预期的有偏差,造成测试失败(failure);而当测试对null的处理结果时,则直接抛出了异常——测试错误(error)。
JUnit将测试失败的情况分为两种:
failure和error。
Failure一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;而error则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),也可能是被测试代码中的一个隐藏的bug。
显然,被测试代码中并没有对首字母大写和null这两种特殊情况进行处理,现在对源代码进行修改,添加对这两种情况的处理,修改后的代码如下:
packagecom.meritit;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
/**
*对名称、地址等字符串格式的内容进行格式检查
*@authorlvxg
*/
publicclassWordDealUtil{
/**
*将Java对象名称(每个单词的头字母大写)按照
*数据库命名的习惯进行格式化
*格式化后的数据为小写字母,并且使用下划线分割命名单词
*
*例如:
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();
}
}
再次运行测试,显示的测试结果如图5所示:
至此,现在的代码已经比较稳定,可以作为API的一部分提供给其它模块使用了,如果测试还没有通过,则继续进行代码改进,一直等到测试完全通过时编码才完成。
您的单元测试代码不是用来证明您是对的,而是为了证明您没有错。
因此单元测试的范围要全面,比如对边界值、正常值、错误值的测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。
四.JUnit深入
当然,JUnit提供的功能决不仅仅如此简单,在接下来的内容中,我们会看到JUnit中很多有用的特性,掌握它们对您灵活的编写单元测试代码非常有帮助。
4.1Fixture
何谓Fixture?
它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。
在编写单元测试的过程中,您会发现在大部分的测试方法在进行真正的测试之前都需要做大量的铺垫——为设计准备Fixture而忙碌。
这些铺垫过程占据的代码往往比真正测试的代码多得多,而且这个比率随着测试的复杂程度的增加而递增。
当多个测试方法都需要做同样的铺垫时,重复代码的“坏味道”便在测试代码中弥漫开来。
这股“坏味道”会弄脏您的代码,还会因为疏忽造成错误,应该使用一些手段来根除它。
JUnit专门提供了设置公共Fixture的方法,同一测试类中的所有测试方法都可以共用它来初始化Fixture和注销Fixture。
和编写JUnit测试方法一样,公共Fixture的设置也很简单,您只需要:
1.使用注解org,junit.Before修饰用于初始化Fixture的方法。
2.使用注解org.junit.After修饰用于注销Fixture的方法。
3.保证这两种方法都使用publicvoid修饰,而且不能带有任何参数。
遵循上面的三条原则,编写出的代码大体是这个样子:
//初始化Fixture方法
@Beforepublicvoidinit(){……}
//注销Fixture方法
@Afterpublicvoiddestroy(){……}
这样,在每一个测试方法执行之前,JUnit会保证init方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit又会调用destroy方法注销测试环境。
注意是每一个测试方法的执行都会触发对公共Fixture的设置,也就是说使用注解Before或者After修饰的公共Fixture设置方法是方法级别的(图6)。
这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。
图6
可是,这种Fixture设置方式还是引来了批评,因为它效率低下,特别是在设置Fixture非常耗时的情况下(例如设置数据库链接)。
而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个测试方法重新设置一次Fixture。
因此在JUnit4中引入了类级别的Fixture设置方法,编写规范如下:
1.使用注解org,junit.BeforeClass修饰用于初始化Fixture的方法。
2.使用注解org.junit.AfterClass修饰用于注销Fixture的方法。
3.保证这两种方法都使用publicstaticvoid修饰,而且不能带有任何参数。
类级别的Fixture仅会在测试类中所有测试方法执行之前执行初始化,并在全部测试方法测试完毕之后执行注销方法(图7)。
代码范本如下:
//类级别Fixture初始化方法
@BeforeClasspublicstaticvoiddbInit(){……}
//类级别Fixture注销方法
@AfterClasspublicstaticvoiddbClose(){……}
图7
4.2异常以及时间测试
注解org.junit.Test中有两个非常有用的参数:
expected和timeout。
参数expected代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,则JUnit会认为这个测试没有通过。
这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。
举例来说,方法TestIndexOfBoundsException用于检查数组是否越界。
测试方法testException在数组越界时的方法如下:
packagecom.meritit;
importorg.junit.Test;
publicclassTestIndexOfBoundsException{
@Test(expected=IndexOutOfBoundsException.class)
publicvoidtestException(){
String[]str={"one","two","three"};
System.out.println("four:
"+str[3]);
}
}
注解org.junit.Test的另一个参数timeout,指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,则JUnit认为测试失败。
这个参数对于性能测试有一定的帮助。
例如,如果解析一份自定义的XML文档花费了多于1秒的时间,就需要重新考虑XML结构的设计,那单元测试方法可以这样来写:
@Test(timeout=1000)
publicvoidselfXMLReader(){
……
}
4.3忽略测试方法
JUnit提供注解org.junit.Ignore用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行。
例如下面的代码便表示由于没有了数据库链接,提示JUnit忽略测试方法unsupportedDBCheck:
@Ignore("dbisdown")
@Test(expected=UnsupportedDBVersionException.class)
publicvoidunsupportedDBCheck(){
……
}
但是一定要小心。
注解org.junit.Ignore只能用于暂时的忽略测试,如果需要永远忽略这些测试,一定要确认被测试代码不再需要这些测试方法,以免忽略必要的测试点。
4.4测试运行器
又一个新概念出现了——测试运行器,JUnit中所有的测试方法都是由它负责执行的。
JUnit为单元测试提供了默认的测试运行器,但JUnit并没有限制您必须使用默认的运行器。
相反,您不仅可以定制自己的运行器(所有的运行器都继承自org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。
指定方法也很简单,使用注解org.junit.runner.RunWith在测试类上显式的声明要使用的运行器即可:
@RunWith(
CustomTestRunner.class)
publicclassTestWordDealUtil{
……
}
4.5测试套件
在实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在我们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。
为了解决这个问题,JUnit提供了一种批量运行测试类的方法,叫做测试套件。
这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。
测试套件的写法非常简单,您只需要遵循以下规则:
1.创建一个空类作为测试套件的入口。
2.使用注解org.junit.runner.RunWith和org.junit.runners.Suite.SuiteClasses修饰这个空类。
3.将org.junit.runners.Suite作为参数传入注解RunWith,以提示JUnit为此类使用套件运行器执行。
4.将需要放入此测试套件的测试类组成数组作为注解SuiteClasses的参数。
5.保证这个空类使用public修饰,而且存在公开的不带有任何参数的构造函数。
packagecom.meritit;
importorg.junit.runner.RunWith;
importorg.junit.runners.Suite;
/**
*批量测试工具包中测试类
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({TestWordDealUtil.class})
publicclassRunAllUtilTestsSuite{
}
上例代码中,我们将前文提到的测试类TestWordDealUtil放入了测试套件RunAllUtilTestsSuite中,在Eclipse中运行测试套件,可以看到测试类TestWordDealUtil被调用执行了。
测试套件中不仅可以包含基本的测试类,而且可以包含其它的测试套件,这样可以很方便的分层管理不同模块的单元测试代码。
但是,您一定要保证测试套件之间没有循环包含关系,否则无尽的循环就会出现在您的面前……。
4.6参数化测试
为了保证单元测试的严谨性,我们模拟了不同类型的字符串来测试方法的处理能力,为此我们编写大量的单元测试方法。
可是这些测试方法都是大同小异:
代码结构都是相同的,不同的仅仅是测试数据和期望值。
有没有更好的方法将测试方法中相同的代码结构提取出来,提高代码的重用度,减少复制粘贴代码的烦恼?
在以前的JUnit版本上,并没有好的解决方法,而现在您可以使用JUnit提供的参数化测试方式应对这个问题。
参数化测试的编写稍微有点麻烦(当然这是相对于JUnit中其它特性而言):
1.为准备使用参数化测试的测试类指定特殊的运行器org.junit.runners.Parameterized。
2.为测试类声明几个变量,分别用于存放期望值和测试所用数据。
3.为测试类声明一个使用注解org.junit.runners.Parameterized.Parameters修饰的,返回值为java.util.Collection的公共静态方法,并在此方法中初始化所有需要测试的参数对。
4.为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
5.编写测试方法,使用定义的变量作为参数进行测试。
我们按照这个标准,重新改造一番我们的单元测试代码:
packagecom.meritit;
importstaticorg.junit.Assert.assertEquals;
importjava.util.Arrays;
importjava.util.Collection;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.junit.runners.Parameterized;
importorg.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
publicclassTestWordDealUtilWithParam{
privateStringexpected;
privateStringtarget;
@Parameters
publicstaticCollectionwords(){
returnArrays.asList(newObject[][]{
{"employee_info","employeeInfo"},//测试一般的处理情况
{null,null},//测试null时的处理情况
{"",""},//测试空字符串时的处理情况
{"employee_info","EmployeeInfo"},//测试当首字母大写时的情况
{"employee_info_a","employeeInfoA"},//测试当尾字母为大写时的情况
{"employee_a_info","employeeAInfo"}//测试多个相连字母大写时的情况
});
}
/**
*参数化测试必须的构造函数
*@paramexpected期望的测试结果,对应参数集中的第一个参数
*@paramtarget测试数据,对应参数集中的第二个参数
*/
publicTestWordDealUtilWithParam(Stringexpected,Stringtarget){
this.expected=expected;
this.target=target;
}
/**
*测试将Java对象名称到数据库名称的转换
*/
@TestpublicvoidwordFormat4DB(){
assertEquals(expected,WordDealUtil.wordFormat4DB(target));
}
}
很明显,代码瘦身了。
在静态方法words中,我们使用二维数组来构建测试所需要的参数列表,其中每个数组中的元素的放置顺序并没有什么要求,只要和构造函数中的顺序保持一致就可以了。
现在如果再增加一种测试情况,只需要在静态方法words
中添加相应的数组即可,不再需要复制粘贴出一个新的方法出来了。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 单元测试 JUnit 应用
![提示](https://static.bdocx.com/images/bang_tan.gif)