java单元测试JUnit框架原理与用法实例教程
本文实例讲述了java单元测试JUnit框架原理与用法。分享给大家供大家参考,具体如下:
1简介
JUnit是一个Java语言的单元测试框架,它由KentBeck和ErichGamma建立,逐渐成为xUnit家族中最为成功的一个。JUnit有它自己的JUnit扩展生态圈,多数Java的开发环境都已经集成了JUnit作为单元测试的工具。在这里,一个单元可以是一个方法、类、包或者子系统。因此,单元测试是指对代码中的最小可测试单元进行检查和验证,以便确保它们正常工作。例如,我们可以给予一定的输入测试输出是否是所希望得到的结果。在本篇博客中,作者将着重介绍JUnit4.X版本的特性,这也是我们在日常开发中使用最多的版本。
2特点
JUnit提供了注释以及确定的测试方法;
JUnit提供了断言用于测试预期的结果;
JUnit测试优雅简洁不需要花费太多的时间;
JUnit测试让大家可以更快地编写代码并且提高质量;
JUnit测试可以组织成测试套件包含测试案例,甚至其他测试套件;
Junit显示测试进度,如果测试是没有问题条形是绿色的,测试失败则会变成红色;
JUnit测试可以自动运行,检查自己的结果,并提供即时反馈,没有必要通过测试结果报告来手动梳理。
3内容
3.1注解
@Test:该注释表示,用其附着的公共无效方法(即用public修饰的void类型的方法)可以作为一个测试用例;
@Before:该注释表示,用其附着的方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件;
@BeforeClass:该注释表示,用其附着的静态方法必须执行一次并在类的所有测试之前,发生这种情况时一般是测试计算共享配置方法,如连接到数据库;
@After:该注释表示,用其附着的方法在执行每项测试后执行,如执行每一个测试后重置某些变量,删除临时变量等;
@AfterClass:该注释表示,当需要执行所有的测试在JUnit测试用例类后执行,AfterClass注解可以使用以清理建立方法,如断开数据库连接,注意:附有此批注(类似于BeforeClass)的方法必须定义为静态;
@Ignore:该注释表示,当想暂时禁用特定的测试执行可以使用忽略注释,每个被注解为@Ignore的方法将不被执行。
/**
*JUnit注解示例
*/
@Test
publicvoidtestYeepay(){
Syetem.out.println("用@Test标示测试方法!");
}
@AfterClass
publicstaticvoidpaylus(){
Syetem.out.println("用@AfterClass标示的方法在测试用例类执行完之后!");
}
3.2断言
在这里,作者将介绍一些断言方法,所有这些方法都来自org.junit.Assert类,其扩展了java.lang.Object类并为它们提供编写测试,以便检测故障。简而言之,我们就是通过断言方法来判断实际结果与我们预期的结果是否相同,如果相同,则测试成功,反之,则测试失败。
voidassertEquals([Stringmessage],expectedvalue,actualvalue):断言两个值相等,值的类型可以为int、short、long、byte、char或者
java.lang.Object,其中第一个参数是一个可选的字符串消息;
voidassertTrue([Stringmessage],booleancondition):断言一个条件为真;
voidassertFalse([Stringmessage],booleancondition):断言一个条件为假;
voidassertNotNull([Stringmessage],java.lang.Objectobject):断言一个对象不为空(null);
voidassertNull([Stringmessage],java.lang.Objectobject):断言一个对象为空(null);
voidassertSame([Stringmessage],java.lang.Objectexpected,java.lang.Objectactual):断言两个对象引用相同的对象;
voidassertNotSame([Stringmessage],java.lang.Objectunexpected,java.lang.Objectactual):断言两个对象不是引用同一个对象;
voidassertArrayEquals([Stringmessage],expectedArray,resultArray):断言预期数组和结果数组相等,数组的类型可以为int、long、short、char、byte或者java.lang.Object
4JUnit3.X和JUnit4.X的区别
4.1JUnit3.X
(1)使用JUnit3.X版本进行单元测试时,测试类必须要继承于TestCase父类;
(2)测试方法需要遵循的原则:
①public的;
②void的;
③无方法参数;
④方法名称必须以test开头;
(3)不同的测试用例之间一定要保持完全的独立性,不能有任何的关联;
(4)要掌握好测试方法的顺序,不能依赖于测试方法自己的执行顺序。
/**
*用JUnit3.X进行测试
*/
importjunit.framework.Assert;
importjunit.framework.TestCase;
publicclassTestOperationextendsTestCase{
privateOperationoperation;
publicTestOperation(Stringname){//构造函数
super(name);
}
@Override
publicvoidsetUp()throwsException{//在每个测试方法执行[之前]都会被调用,多用于初始化
System.out.println("欢迎使用Junit进行单元测试...");
operation=newOperation();
}
@Override
publicvoidtearDown()throwsException{//在每个测试方法执行[之后]都会被调用,多用于释放资源
System.out.println("Junit单元测试结束...");
}
publicvoidtestDivideByZero(){
Throwablete=null;
try{
operation.divide(6,0);
Assert.fail("测试失败");//断言失败
}catch(Exceptione){
e.printStackTrace();
te=e;
}
Assert.assertEquals(Exception.class,te.getClass());
Assert.assertEquals("除数不能为0",te.getMessage());
}
}
4.2JUnit4.X
(1)使用JUnit4.X版本进行单元测试时,不用测试类继承TestCase父类;
(2)JUnit4.X版本,引用了注解的方式进行单元测试;
(3)JUnit4.X版本我们常用的注解包括:
@Before注解:与JUnit3.X中的setUp()方法功能一样,在每个测试方法之前执行,多用于初始化;
@After注解:与JUnit3.X中的tearDown()方法功能一样,在每个测试方法之后执行,多用于释放资源;
@Test(timeout=xxx)注解:设置当前测试方法在一定时间内运行完,否则返回错误;
@Test(expected=Exception.class)注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class;
此外,我们可以通过阅读上面的第二部分“2注解”了解更多的注解。
/**
*用JUnit4.X进行测试
*/
importstaticorg.junit.Assert.*;
importorg.junit.After;
importorg.junit.AfterClass;
importorg.junit.Before;
importorg.junit.BeforeClass;
importorg.junit.Test;
publicclassTestOperation{
privateOperationoperation;
@BeforeClass
publicstaticvoidglobalInit(){//在所有方法执行之前执行
System.out.println("@BeforeClass标注的方法,在所有方法执行之前执行...");
}
@AfterClass
publicstaticvoidglobalDestory(){//在所有方法执行之后执行
System.out.println("@AfterClass标注的方法,在所有方法执行之后执行...");
}
@Before
publicvoidsetUp(){//在每个测试方法之前执行
System.out.println("@Before标注的方法,在每个测试方法之前执行...");
operation=newOperation();
}
@After
publicvoidtearDown(){//在每个测试方法之后执行
System.out.println("@After标注的方法,在每个测试方法之后执行...");
}
@Test(timeout=600)
publicvoidtestAdd(){//设置限定测试方法的运行时间如果超出则返回错误
System.out.println("测试add方法...");
intresult=operation.add(2,3);
assertEquals(5,result);
}
@Test
publicvoidtestSubtract(){
System.out.println("测试subtract方法...");
intresult=operation.subtract(1,2);
assertEquals(-1,result);
}
@Test
publicvoidtestMultiply(){
System.out.println("测试multiply方法...");
intresult=operation.multiply(2,3);
assertEquals(6,result);
}
@Test
publicvoidtestDivide(){
System.out.println("测试divide方法...");
intresult=0;
try{
result=operation.divide(6,2);
}catch(Exceptione){
fail();
}
assertEquals(3,result);
}
@Test(expected=Exception.class)
publicvoidtestDivideAgain()throwsException{
System.out.println("测试divide方法,除数为0的情况...");
operation.divide(6,0);
fail("testError");
}
publicstaticvoidmain(String[]args){
}
}
4.3特别提醒
通过以上两个例子,我们已经可以大致知道JUnit3.X和JUnit4.X两个版本的区别啦!首先,如果我们使用JUnit3.X,那么在我们写的测试类的时候,一定要继承TestCase类,但是如果我们使用JUnit4.X,则不需继承TestCase类,直接使用注解就可以啦!在JUnit3.X中,还强制要求测试方法的命名为“testXxxx”这种格式;在JUnit4.X中,则不要求测试方法的命名格式,但作者还是建议测试方法统一命名为“testXxxx”这种格式,简洁明了。
此外,在上面的两个示例中,我们只给出了测试类,但是在这之前,还应该有一个被测试类,也就是我们真正要实现功能的类。现在,作者将给出上面示例中被测试的类,即Operation类:
/**
*定义了加减乘除的法则
*/
publicclassOperation{
publicstaticvoidmain(String[]args){
System.out.println("a+b="+add(1,2));
System.out.println("a-b="+subtract(1,2));
System.out.println("a*b="+multiply(1,2));
System.out.println("a/b="+divide(4,2));
System.out.println("a/b="+divide(1,0));
}
publicstaticintadd(inta,intb){
returna+b;
}
publicstaticintsubtract(inta,intb){
returna-b;
}
publicstaticintmultiply(inta,intb){
returna*b;
}
publicstaticintdivide(inta,intb){
returna/b;
}
}
5测试示例
5.1示例一:简单的JUnit3.X测试
importjunit.framework.Test;
importjunit.framework.TestCase;
importjunit.framework.TestSuite;
importjava.util.ArrayList;
importjava.util.Collection;
/**
*1、创建一个测试类,继承TestCase类
*/
publicclassSimpleTestDemoextendsTestCase{
publicSimpleTestDemo(Stringname){
super(name);
}
/**
*2、写一个测试方法,断言期望的结果
*/
publicvoidtestEmptyCollection(){
Collectioncollection=newArrayList();
assertTrue(collection.isEmpty());
}
/**
*3、写一个suite()方法,它会使用反射动态的创建一个包含所有的testXxxx方法的测试套件
*/
publicstaticTestsuit(){
returnnewTestSuite(SimpleTestDemo.class);
}
/**
*4、写一个main()方法,以文本运行器的方式方便的运行测试
*/
publicstaticvoidmain(String[]args){
junit.textui.TestRunner.run(suit());
}
}
5.2示例二:套件测试
首先,介绍一下套件测试,简单来讲,测试套件是指:一些测试不同类的用例,可以使用@RunWith和@Suite注解把所有的测试类套在一起,从而形成测试套件。如果有很多测试类,想让它们都运行在同一时间,而不是单一地运行每个测试,套件测试是非常有用的。当一个类被注解为@RunWith,JUnit将调用其中的注解,以便运行测试类,而不使用内置的JUnit运行方法。
/**
*待测试类
*/
importjava.util.Arrays;
publicclassGotoWork{
publicString[]prepareSkills(){
String[]skill={"Java","MySQL","JSP"};
System.out.println("Myskillsinclude:"+Arrays.toString(skill));
returnskill;
}
publicString[]addSkills(){
String[]skill={"Java","MySQL","JSP","JUnit"};
System.out.println("Look,myskillsinclude:"+Arrays.toString(skill));
returnskill;
}
}
/**
*测试类1
*/
importorg.junit.Test;
importstaticorg.junit.Assert.*;
publicclassPrepareSkillsTest{
GotoWorkgotoWork=newGotoWork();
String[]skill={"Java","MySQL","JSP"};
@Test
publicvoidtestPrepareSkills(){
System.out.println("InsidetestPrepareSkills()");
assertArrayEquals(skill,gotoWork.prepareSkills());
}
}
/**
*测试类2
*/
importorg.junit.Test;
importstaticorg.junit.Assert.*;
publicclassAddSkillsTest{
GotoWorkgotoWork=newGotoWork();
String[]skill={"Java","MySQL","JSP","JUnit"};
@Test
publicvoidtestAddSkills(){
System.out.println("InsidetestAddPencils()");
assertArrayEquals(skill,gotoWork.addSkills());
}
}
/**
*套件测试
*/
importorg.junit.runner.RunWith;
importorg.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({PrepareSkillsTest.class,AddSkillsTest.class})
publicclassSuitTest{
}
使用@Suite.SuiteClasses注解,可以定义测试类,将被列入执行,并且执行的顺序就是在@Suite.SuiteClasses注解中定义的顺序。
5.3示例三:参数化测试
首先介绍一下参数化测试,一个测试类可以被看作是一个参数化测试类,当其满足下列所有要求:
①该类被注解为@RunWith(Parameterized.class);
②该类有一个构造函数,存储测试数据;
③该类有一个静态方法生成并返回测试数据,并标注@Parameters注解;
④该类有一个测试方法,即用注解@Test标注的方法。
/**
*待测试类
*/
publicclassCalculate{
publicintsum(intvar1,intvar2){
System.out.println("此方法的参数值分别为:"+var1+"+"+var2);
returnvar1+var2;
}
}
/**
*参数化测试类
*/
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)
publicclassCalculateTest{
privateintexpected;
privateintfirst;
privateintsecond;
publicCalculateTest(intexpectedResult,intfirstNumber,intsecondNumber){
this.expected=expectedResult;
this.first=firstNumber;
this.second=secondNumber;
}
@Parameters
publicstaticCollectionaddedNumbers(){
returnArrays.asList(newInteger[][]{{3,1,2},{5,2,3},{7,3,4},{9,4,5},});
}
@Test
publicvoidtestSum(){
Calculateadd=newCalculate();
System.out.println("Additionwithparameters:"+first+"and"+second);
assertEquals(expected,add.sum(first,second));
}
}
观察CalculateTest类,它满足上述所有的要求,因此它就可以称为一个参数化测试类。addedNumbers方法使用注释@Parameters返回数组的集合,每个数组包括每个测试执行输入和输出数字,每个数组中的元素数必须相同好与构造参数的个数相匹配。所以,在这种特定的情况下,每个数组包括三个元素,即表示要加入的两个元素和一个结果元素。
6个人建议
有些童鞋可能会有一些误解,认为写测试代码没有用,而且还会增大自己的压力,浪费时间。但事实上,写测试代码与否,还是有很大区别的,如果是在小的项目中,或许这种区别还不太明显,但如果在大型项目中,一旦出现错误或异常,用人力去排查的话,那将会浪费很多时间,而且还不一定排查的出来,但是如果用测试代码的话,JUnit就是自动帮我们判断一些代码的结果正确与否,从而节省的时间将会远远超过你写测试代码的时间。
因此,个人建议:要养成编写测试代码的习惯,码一点、测一点;再码一点,再测一点,如此循环。在我们不断编写与测试代码的过程中,我们将会对类的行为有一个更为深入的了解,从而可以有效的提高我们的工作效率。下面,作者就给出一些具体的编写测试代码的技巧和较好的实践方法:
1.不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法;
2.不要依赖或假定测试运行的顺序,因为JUnit会利用Vector保存测试方法,所以不同的平台会按不同的顺序从Vector中取出测试方法;
3.避免编写有副作用的TestCase,例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据,只需要简单的回滚就可以了;
4.当继承一个测试类时,记得调用父类的setUp()和tearDown()方法;
5.将测试代码和工作代码放在一起,同步编译和更新;
6.测试类和测试方法应该有一致的命名方案,如在工作类名前加上test从而形成测试类名;
7.确保测试与时间无关,不要使用过期的数据进行测试,以至于导致在随后的维护过程中很难重现测试;
8.如果编写的软件面向国际市场,那么编写测试时一定要考虑国际化的因素;
9.尽可能地利用JUnit提供地assert和fail方法以及异常处理的方法,其可以使代码更为简洁;
10.测试要尽可能地小,执行速度快;
11.不要硬性规定数据文件的路径;
12.使用文档生成器做测试文档。
事实上,在Junit中使用trycatch来捕获异常是没有必要的,因为Junit会自动捕获异常,那些没有被捕获的异常就会被当成错误处理啦!
更多关于java相关内容感兴趣的读者可查看本站专题:《Java数据结构与算法教程》、《Java字符与字符串操作技巧总结》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》
希望本文所述对大家java程序设计有所帮助。