Unitils教程单元测试应该很容易,直观....至少在理论上是这样的。然而现实的项目通常跨越多个层次,有的是数据驱动有的使用中间件技术,比如EJB和Hibernate等等。Unitils源于尝试更加务实的单元测试,它始于一套测试准则,并为了方便应用这些准则而开发了一个开源代码库。本教程将通过一些实例向您展示如何在您的项目中使用Unitils。断言应用数据库测试应用Spring测试应用mockobjects测试应用EasyMock支持Assertionutilities断言应用应用反射的断言典型的单体测试一般都包含一个重要的组成部分:对比实际产生的结果和希望的结果是否一致的方法:断言方法(assertEquals)。Unitils为我们提供了一个非常实用的assertion方法,让我们用比较两个USER对象的实例(User包括id,firstname,lastname属性)来开始我们这一部分的介绍。publicclassUser{privatelongid;privateStringfirst;privateStringlast;publicUser(longid,Stringfirst,Stringlast){this.id=id;this.first=first;this.last=last;}}Useruser1=newUser(1,John,Doe);Useruser2=newUser(1,John,Doe);assertEquals(user1,user2);因为两个user包含相同的属性,所以你一定以为断言是成功的。但是事实恰恰相反,断言失败,因为user类没有覆写equals()方法,所以断言就用判断两个对象是否相等来来返回结果,换句话说就是采用了user1==user2的结果,用两个对象的引用是否一致作为判断的依据。假如你像下面这样重写equals方法,publicbooleanequals(Objectobject){if(objectinstanceofUser){returnid==((User)object).id;}returnfalse;}也许通过判断两个USER的ID是否相等来判断这两个user是否相等在您的程序逻辑里是行得通的,但是在单体测试里未必是有意义的,因为判断两个user是否相等被简化成了user的id是否相等了。Useruser1=newUser(1,John,Doe);Useruser2=newUser(1,Jane,Smith);assertEquals(user1,user2);按照上面的代码逻辑,也许断言成功了,但是这是您期望的么?所以最好避免使用equals()方法来实现两个对象的比较(除非对象的属性都是基本类型)。对了,还有一个办法也许能够有效,那就是把对象的属性一个一个的比较。Useruser1=newUser(1,John,Doe);Useruser2=newUser(1,John,Doe);assertEquals(user1.getId(),user2.getId());assertEquals(user1.getFirst(),user2.getFirst());assertEquals(user1.getLast(),user2.getLast());Unitils其实为我们提供了非常简单的方法,一种采用反射的方法。使用ReflectionAssert.assertReflectionEquals方法,上面的代码可以重写如下:Useruser1=newUser(1,John,Doe);Useruser2=newUser(1,John,Doe);assertReflectionEquals(user1,user2);这种断言采用反射机制,循环的比较两个对象的filed的值,比如上面的例子,它就是依次对比id,first,last的值是否相等。如果某个filed本身就是object,那么断言会递归的依次比对这两个object的所有filed,对于Arrays,Maps,collection也是一样的,会通过反射机制递归的比较所有的element,如果值的类型是基本类型(int,long,...)或者基本类型的包装类(Integer,Long,...),就会比较值是否相等(using==)。看看下面的代码,这回断言成功了!assertReflectionEquals(1,1L);ListDoublemyList=newArrayListDouble();myList.add(1.0);myList.add(2.0);assertReflectionEquals(Arrays.asList(1,2),myList);宽松式断言源于对代码可维护性的原因,只添加对测试有益的断言是十分重要的。让我用一个例子来说明这一点:假如一个计算accountbalance的测试代码,那么就没有对bank-customer的name进行断言的必要,因为这样就增加了测试代码的复杂度,让人难于理解,更重要的是当代码发生变化时增加了测试代码的脆弱性。为了让你的测试代码更容易的适应其他代码的重构,那么一定保证你的断言和测试数据是建立在测试范围之内的。为了帮助我们写出这样的测试代码,ReflectionAssert方法为我们提供了各种级别的宽松断言。下面我们依次介绍这些级别的宽松断言。顺序是宽松的:第一种宽松级别就是忽略collection或者array中元素的顺序。其实我们在应用list的时候往往对元素的顺序是不关心的。比如:一个代码想要搜索出所有无效的银行账号,那么返回的结果的顺序就对我们业务逻辑没什么影响。为了实现这种宽松模式,ReflectionAssert.assertReflectionEquals方法可以通过配置来实现对顺序的忽略,只要ReflectionAssert.assertReflectionEquals方法设置ReflectionComparatorMode.LENIENT_ORDER参数就可以了。比如:ListIntegermyList=Arrays.asList(3,2,1);assertReflectionEquals(Arrays.asList(1,2,3),myList,LENIENT_ORDER);忽略缺省值第二种宽松方式是:如果断言方法被设置为ReflectionComparatorMode.IGNORE_DEFAULTS模式的话,java的defaultvalues比如objects是null值是0或者false,那么断言忽略这些值的比较,换句话说就是断言只会比较那些你初始化了的期望值,如果你没有初始化一些filed,那么断言就不会去比较它们。还是拿个例子说明比较好,假设有一个user类:有firstname,lastname,street...field属性,但是你只想比较两个对象实例的firstname和street的值,其他的属性值你并不关心,那么就可以像下面这么比较了。UseractualUser=newUser(John,Doe,newAddress(Firststreet,12,Brussels));UserexpectedUser=newUser(John,null,newAddress(Firststreet,null,null));assertReflectionEquals(expectedUser,actualUser,IGNORE_DEFAULTS);你想忽略的属性值设置为null那么一定把它放到左边参数位置(=expected),如果只有右边参数的值为null,那么断言仍然会比较的。assertReflectionEquals(null,anyObject,IGNORE_DEFAULTS);//SucceedsassertReflectionEquals(anyObject,null,IGNORE_DEFAULTS);//Fails宽松的date第三种宽松模式是ReflectionComparatorMode.LENIENT_DATES,这种模式只会比较两个实例的date是不是都被设置了值或者都为null,而忽略date的值是否相等,如果你想严格比较对象的每一个域,而又不想去比较时间的值是不是相等,那么这种模式就是合适你的。DateactualDate=newDate(44444);DateexpectedDate=newDate();assertReflectionEquals(expectedDate,actualDate,LENIENT_DATES);assertLenientEquals方法ReflectionAssert类为我们提供了具有两种宽松模式的断言:既忽略顺序又忽略缺省值的断言assertLenientEquals,使用这种断言上面两个例子就可以简化如下了:ListIntegermyList=Arrays.asList(3,2,1);assertLenientEquals(Arrays.asList(1,2,3),myList);assertLenientEquals(null,any);//SucceedsassertLenientEquals(any,null);//FailsassertReflection...以这种方式命名的断言是默认严格模式但是可以手动设置宽松模式的断言,assertLenient...以这种方式命名的断言是具有忽略顺序和忽略缺省值的断言。属性断言assertLenientEquals和assertReflectionEquals这两个方法是把对象作为整体进行比较,ReflectionAssert类还给我们提供了只比较对象的特定属性的方法:assertPropertyLenientEquals和assertPropertyReflectionEquals,比如:assertPropertyLenientEquals(id,1,user);assertPropertyLenientEquals(address.street,Firststreet,user);这个方法的参数也支持集合对象,下面的例子就会比较特定的属性的集合中的每一个元素是否相等。assertPropertyLenientEquals(id,Arrays.asList(1,2,3),users);assertPropertyLenientEquals(address.street,Arrays.asList(Firststreet,Secondstreet,Thirdstreet),users);同样每一种方法都提供两个版本,assertPropertyReflectionEquals和assertPropertyLenientEquals.assertPropertyReflection...以这种方式命名的断言是默认严格模式但是可以手动设置宽松模式的断言,assertPropertyLenient...以这种方式命名的断言是具有忽略顺序和忽略缺省值的断言。数据库测试对于商业应用程序来说数据库层的单体测试是十分重要的,但是却常常被放弃了,因为太复杂了。Unitils大大减少了这种复杂度而且可维护。下面就介绍支持DatabaseModule和DbUnitModule的数据库测试。通过DbUnit来管理测试数据数据库测试运行在一个单体测试数据库上,它提供完整且易于管理的测试数据,DbUnitModule在Dbunit的基础上提供对测试数据集的支持。加载测试数据集还是让我们以一个简单的例子开始,findByName方法通过first和lastname抽取一个User。典型的单体测试代码如下:@DataSetpublicclassUserDAOTestextendsUnitilsJUnit4{@TestpublicvoidtestFindByName(){Userresult=userDa