單元測試系列之3:測試整合之王Unitils

引述:程序測試對保障應用程序正確 性而言,其重要性怎麼樣強調都不爲過。JUnit是必須事先掌握的測試框架,大多數測試框架和測試工具都在此基礎上擴展而來,Spring對測試所提供的 幫助類也是在JUnit的基礎上進行演化的。直接使用JUnit測試基於Spring的應用存在諸多不便,不可避免地須要將大量的精力用於應付測試夾具準 備、測試現場恢復、訪問測試數據操做結果等邊緣性的工做中。Mockito、Unitils、Dbunit等框架的出現,這些問題有了很好的解決方案,特 別是Unitils結合Dbunit對測試DAO層提供了強大的支持,大大提升了編寫測試用例的效率和質量。

   Unitils海納百川(Junit,dbunit,mockito spring hibernate and so on..),以打造一個在實際應用開發中真正實戰的測試框架,是致力於應用實戰的開發者不得不學習的開源框架。

Unitils概述

   Unitils測試框架目的是讓單元測試變得更加容易和可維護。Unitils構建在DbUnit與EasyMock項目之上並與JUnit和 TestNG相結合。支持數據庫測試,支持利用Mock對象進行測試並提供與Spring和Hibernate相集成。Unitils設計成以一種高度可 配置和鬆散耦合的方式來添加這些服務到單元測試中,目前其最新版本是3.1。

Unitils功能特色
java

  •  自動維護和強制關閉單元測試數據庫(支持Oracle、Hsqldb、MySQL、DB2)。
  •  簡化單元測試數據庫鏈接的設置。
  •  簡化利用DbUnit測試數據的插入。
  •  簡化Hibernate session管理。
  •  自動測試與數據庫相映射的Hibernate映射對象。
  •  易於把Spring管理的Bean注入到單元測試中,支持在單元測試中使用Spring容器中的Hibernate SessionFactory。
  •  簡化EasyMock Mock對象建立。
  •  簡化Mock對象注入,利用反射等式匹配EasyMock參數。



Unitils模塊組件

   Unitils經過模塊化的方式來組織各個功能模塊,採用相似於Spring的模塊劃分方式,如unitils-core、unitils-database、unitils-mock等。比之前整合在一個工程裏面顯得更加清晰,目前全部模塊以下所示:spring

  •   unitils-core:核心內核包。
  •   unitils-database:維護測試數據庫及鏈接池。
  •   unitils-DbUnit:使用DbUnit來管理測試數據。
  •   unitils-easymock:支持建立Mock和寬鬆的反射參數匹配。
  •   unitils-inject:支持在一個對象中注入另外一個對象。
  •   unitils-mock:整合各類Mock,在Mock的使用語法上進行了簡化。
  •   unitils-orm:支持Hibernate、JPA的配置和自動數據庫映射檢查。
  •   unitils-spring:支持加載Spring的上下文配置,並檢索和Spring Bean注入。


   Unitils的核心架構中包含Moudule和TestListener兩個概念,相似Spring中黏連其餘開源軟件中的FactoryBean概念。能夠當作第三方測試工具的一個黏合劑。總體框架如圖16-4所示:



   經過TestListener能夠在測試運行的不一樣階段注入某些功能。同時某一個TestListener又被一個對應的Module所持有。 Unitils也能夠當作一個插件體系結構,TestListener在整個Unitils中又充當了插件中擴展點的角色,從TestListener這 個接口中咱們能夠看到,它能夠在crateTestObject、before(after)Class、 before(after)TestMethod、beforeSetup、afterTeardown的不一樣切入點添加不一樣的動做。

Unitils配置文件 sql

  •   unitils-defaults.properties:默認配置文件,開啓全部功能。
  •   unitils.properties:項目級配置文件,用於項目通用屬性配置。
  •   unitils-local.properties:用戶級配置文件,用於我的特殊屬性配置。


    Unitils的配置定義了通常配置文件的名字unitils.properties和用戶自定義配置文件unitils- local.properties,並給出了默認的模塊及模塊對應的className,便於Unitils加載對應的模塊module。可是若是用戶分 別在unitils.properties文件及unitils -local.properties文件中對相同屬性配置不一樣值時,將會以unitils-local.properties 的配置內容爲主。

Unitils斷言

   典型的單元測試通常都包含一個重要的組成部分:對比實際產生的結果和但願的結果是否一致的方法,即斷言方法(assertEquals)。Unitils 爲咱們提供了一個很是實用的斷言方法,咱們以第2章中編寫的用戶領域對象User爲藍本,比較兩個User對象的實例來開始認識Unitils的斷言之 旅。

assertReflectionEquals:反射斷言

    在Java世界中,要比較現有兩個對象實例是否相等,若是類沒有重寫equals()方法,用兩個對象的引用是否一致做爲判斷依據。有時候,咱們並不須要 關注兩個對象是否引用同一個對象,只要兩個對象的屬性值同樣就能夠了。在JUnit單元測試中,有兩種測試方式進行這樣的場景測試:一是在比較實體類中重 寫equals()方法,而後進行對象比較;二是把對象實例的屬性一個一個進行比較。無論採用哪一種方法,都比較煩鎖,Unitils爲咱們提供了一種很是 簡單的方法,即便用ReflectionAssert.assertReflectionEquals方法, 如代碼清單16-11所示: 數據庫

Java代碼   收藏代碼
  1. package com.baobaotao.test;  
  2. import java.util.*;  
  3. import org.junit.Test;  
  4. import static org.unitils.reflectionassert.ReflectionAssert.*;  
  5. import static org.unitils.reflectionassert.ReflectionComparatorMode.*;  
  6. import com.baobaotao.domain.User;  
  7.   
  8. public class AssertReflectionEqualsTest {  
  9.     @Test  
  10.     public void testReflection(){  
  11.         User user1 = new User("tom","1234");  
  12.         User user2 = new User("tom","1234");  
  13.         ReflectionAssert.assertReflectionEquals(user1, user2);  
  14.     }  
  15.   
  16. }  



    ReflectionAssert. AssertReflectionEquals(指望值,實際值,比較級別)方法爲咱們提供了各類級別的比較斷言。下面咱們依次介紹這些級別的比較斷言。
    ReflectionComparatorMode.LENIENT_ORDER:忽略要斷言集合collection 或者array 中元素的順序。
    ReflectionComparatorMode.IGNORE_DEFAULTS:忽略Java類型默認值,如引用類型爲null,整型類型爲0,或者布爾類型爲false時,那麼斷言忽略這些值的比較。
    ReflectionComparatorMode.LENIENT_DATES:比較兩個實例的Date是否是都被設置了值或者都爲null,而忽略Date的值是否相等。

assertLenientEquals:斷言

   ReflectionAssert 類爲咱們提供了兩種比較斷言:既忽略順序又忽略默認值的斷言assertLenientEquals,使用這種斷言就能夠進行簡單比較。下面經過實例學習其具體的用法,如代碼清單16-12所示。數組

Java代碼   收藏代碼
  1. package com.baobaotao.test;  
  2. import java.util.*;  
  3. …  
  4. public class AssertReflectionEqualsTest {  
  5.       Integer orderList1[] = new Integer[]{1,2,3};  
  6.       Integer orderList2[] = new Integer[]{3,2,1};  
  7.   
  8.           
  9.         //① 測試兩個數組的值是否相等,忽略順序  
  10.         //assertReflectionEquals(orderList1, orderList2,LENIENT_ORDER);  
  11. assertLenientEquals(orderList1, orderList2);  
  12.   
  13.           
  14.         //② 測試兩個對象的值是否相等,忽略時間值是否相等  
  15.         User user1 = new User("tom","1234");  
  16.         Calendar cal1 = Calendar.getInstance();  
  17.         user1.setLastVisit(cal1.getTime());  
  18.         User user2 = new User("tom","1234");  
  19.         Calendar cal2 = Calendar.getInstance();  
  20.         cal2.set(Calendar.DATE, 15);  
  21.         user2.setLastVisit(cal2.getTime());  
  22. //assertReflectionEquals(user1, user2,LENIENT_DATES);  
  23.         assertLenientEquals(user1, user2);  
  24. }  



assertPropertyXxxEquals:屬性斷言

   assertLenientEquals 和assertReflectionEquals 這兩個方法是把對象做爲總體進行比較,ReflectionAssert 類還給咱們提供了只比較對象特定屬性的方法:assertPropertyReflection   Equals()和assertPropertyLenientEquals()。下面經過實例學習其具體的用法,如代碼清單16-13所示。 session

Java代碼   收藏代碼
  1. package com.baobaotao.test;  
  2. import java.util.*;  
  3. …  
  4. public class AssertReflectionEqualsTest {  
  5.       User user = new User("tom","1234");  
  6.       assertPropertyReflectionEquals("userName""tom", user);  
  7.       assertPropertyLenientEquals("lastVisit"null, user);  
  8. }  


   assertPropertyReflectionEquals()斷言是默認嚴格比較模式可是能夠手動設置比較級別的斷言,assertPropertyLenientEquals()斷言是具備忽略順序和忽略默認值的斷言。


集成Spring

   Unitils 提供了一些在Spring 框架下進行單元測試的特性。Spring 的一個基本特性就是,類要設計成爲沒有Spring 容器或者在其餘容器下仍然易於進行單元測試。可是不少時候在Spring 容器下進行測試仍是很是有用的。
   Unitils 提供瞭如下支持 Spring 的特性:架構

  •   ApplicationContext 配置的管理;
  •  在單元測試代碼中注入Spring 的Beans;
  •  使用定義在Spring 配置文件裏的Hibernate SessionFactory;
  •  引用在Spring 配置中Unitils 數據源。



ApplicationContext 配置
   能夠簡單地在一個類、方法或者屬性上加上@SpringApplicationContext 註解,並用Spring的配置文件做爲參數,來加載Spring應用程序上下文。下面咱們經過實例來介紹一下如何建立ApplicationContext。 app

Java代碼   收藏代碼
  1. import org.junit.Test;  
  2. import org.springframework.context.ApplicationContext;  
  3. import org.unitils.UnitilsJUnit4;  
  4. import org.unitils.spring.annotation.SpringApplicationContext;  
  5. import org.unitils.spring.annotation.SpringBean;  
  6. import com.baobaotao.service.UserService;  
  7. import static org.junit.Assert.*;  
  8. //①用戶服務測試  
  9. public class UserServiceTest extends UnitilsJUnit4 {  
  10.   
  11. //①-1 加載Spring配置文件  
  12.     @SpringApplicationContext({"baobaotao-service.xml""baobaotao-dao.xml"})  
  13.     private ApplicationContext applicationContext;  
  14.   
  15. //①-1 加載Spring容器中的Bean  
  16.     @SpringBean("userService")  
  17.     private UserService userService;  
  18.       
  19. //①-3 測試Spring容器中的用戶服務Bean  
  20. @Test  
  21.     public void testUserService (){  
  22.         assertNotNull(applicationContext);  
  23.         assertNotNull(userService.findUserByUserName("tom"));  
  24.     }  
  25. }  
  26. …  



   在①-1處,經過@SpringApplicationContext 註解加載baobaotao-service.xml和baobaotao- dao.xml兩個配置文件,生成一個Spring應用上下文,咱們就能夠在註解的範圍內引用applicationContext這個上下文。在①-2 處,經過@SpringBean註解注入當前Spring容器中相應的Bean,如實例中加載ID爲「userService」的Bean到當前測試範 圍。在①-3處,經過JUnit斷言驗證是否成功加載applicationContext和userService。Unitils加載Spring上 下文的過程是:首先掃描父類的@SpringApplicationContext註解,若是找到了就在加載子類的配置文件以前加載父類的配置文件,這樣 就可讓子類重寫配置文件和加載特定配置文件。
   細心的讀者可能會發現,採用這種方式加載Spring應用上下文,每次執行測試時,都會重複加載Spring應用上下文。Unitils爲咱們提供在類上加載Spring應用上下文的能力,以免重複加載的問題。 框架

Java代碼   收藏代碼
  1. …  
  2. @SpringApplicationContext({"baobaotao-service.xml""baobaotao-dao.xml"})  
  3. public class BaseServiceTest extends UnitilsJUnit4 {  
  4.       
  5.  //加載Spring上下文  
  6.     @SpringApplicationContext  
  7.     public ApplicationContext applicationContext;  
  8.   
  9. }  


  
   在父類BaseServiceTest裏指定了Spring配置文件,Spring應用上下文只會建立一次,而後在子類 SimpleUserServiceTest 裏會重用這個應用程序上下文。加載Spring應用上下文是一個很是繁重的操做,若是重用這個Spring應用上下文就會大大提高測試的性能。dom

Java代碼   收藏代碼
  1. …  
  2. public class SimpleUserServiceTest extends BaseServiceTest {  
  3.       
  4. //① Spring容器中加載Id爲"userService"的Bean  
  5.     @SpringBean("userService")  
  6.     private UserService userService1;  
  7.   
  8. //② 從Spring容器中加載與UserService相同類型的Bean  
  9.     @SpringBeanByType  
  10.     private UserService userService2;  
  11.   
  12.     //③ 從Spring容器中加載與userService相同名稱的Bean  
  13.     @SpringBeanByName  
  14.     private  UserService userService;  
  15.       
  16.      //④ 使用父類的Spring上下文  
  17.     @Test  
  18.     public void testApplicationContext(){  
  19.         assertNotNull(applicationContext);  
  20.     }  
  21.       
  22.     @Test  
  23.     public void testUserService(){  
  24.         assertNotNull(userService.findUserByUserName("tom"));  
  25.         assertNotNull(userService1.findUserByUserName("tom"));  
  26.         assertNotNull(userService2.findUserByUserName("tom"));  
  27.     }  
  28. }  
  29. …  



   在①處,使用@SpringBean 註解從Spring容器中加載一個ID爲userService的Bean。在②處,使用@ SpringBeanByType註解從Spring容器中加載一個與UserService相同類型的Bean,若是找不到相同類型的Bean,就會拋 出異常。在③處,使用@SpringBeanByName 註解從Spring容器中加載一個與當前屬性名稱相同的Bean。

集成Hibernate

   Hibernate是一個優秀的O / R開源框架,它極大地簡化了應用程序的數據訪問層開發。雖然咱們在使用一個優秀的O/R框架,但並不意味咱們無須對數據訪問層進行單元測試。單元測試仍然 很是重要。它不只能夠確保Hibernate映射類的映射正確性,也能夠很便捷地測試HQL查詢等語句。Unitils爲方便測試 Hibernate,提供了許多實用的工具類,如HibernateUnitils就是其中一個,使用 assertMappingWithDatabaseConsistent()方法,就能夠方便測試映射文件的正確性。

SessionFactory 配置

   能夠簡單地在一個類、方法或者屬性上加上@ HibernateSessionFactory 註解,並用Hibernate的配置文件做爲參數,來加載Hibernate上下文。下面咱們經過實例來介紹一下如何建立SessionFactory。

Java代碼   收藏代碼
  1. …  
  2. @HibernateSessionFactory("hibernate.cfg.xml")  
  3. public class BaseDaoTest extends UnitilsJUnit4 {  
  4.     @HibernateSessionFactory  
  5.     public SessionFactory sessionFactory;  
  6.       
  7.   
  8.     @Test  
  9.     public void testSessionFactory(){  
  10.         assertNotNull(sessionFactory);  
  11.     }  
  12. }  



    在父類BaseDaoTest裏指定了Hibernate配置文件,Hibernate應用上下文只會建立一次,而後在子類 SimpleUserDaoTest裏會重用這個應用程序上下文。加載Hibernate應用上下文是一個很是繁重的操做,若是重用這個 Hibernate應用上下文就會大大提高測試的性能。

Java代碼   收藏代碼
  1. …  
  2. public class SimpleUserDaoTest extends BaseDaoTest {  
  3.     private UserDao userDao;  
  4.   
  5. //① 初始化UserDao  
  6. @Before  
  7.     public void init(){  
  8.          userDao = new WithoutSpringUserDaoImpl();  
  9.         userDao.setSessionFactory(sessionFactory); //使用父類的SessionFactory  
  10.     }  
  11.   
  12. //② Hibernate映射測試  
  13. @Test  
  14.     public void testMappingToDatabase() {  
  15.         HibernateUnitils.assertMappingWithDatabaseConsistent();  
  16. }  
  17.   
  18. //③ 測試UserDao  
  19.     @Test  
  20.     public void testUserDao(){  
  21.         assertNotNull(userDao);  
  22.         assertNotNull(userDao.findUserByUserName("tom"));  
  23.         assertEquals("tom", userDao.findUserByUserName("tom").getUserName());  
  24.     }  
  25. }  
  26. …  



   爲了更好演示如何應用Unitils測試基於Hibernate數據訪問層,在這個實例中不使用Spring框架。因此在執行測試時,須要先建立相應的數 據訪問層實例,如實例中的userDao。其建立過程如①處所示,先手工實例化一個UserDao,而後獲取父類中建立的SessionFactory, 並設置到UserDao中。在②處,使用Unitils提供的工具類HibernateUnitils中的方法測試咱們的Hibernate映射文件。在 ③處,經過JUnit的斷言驗證 UserDao相關方法,看是否與咱們預期的結果一致。

集成Dbunit
   
   Dbunit是一個基於JUnit擴展的數據庫測試框架。它提供了大量的類,對數據庫相關的操做進行了抽象和封裝。Dbunit經過使用用戶自定義的數據 集以及相關操做使數據庫處於一種可知的狀態,從而使得測試自動化、可重複和相對獨立。雖然不用Dbunit也能夠達到這種目的,可是咱們必須爲此付出代價 (編寫大量代碼、測試及維護)。既然有了這麼優秀的開源框架,咱們又何須再造輪子。目前其最新的版本是2.4.8。
    隨着Unitils的出現,將Spring、Hibernate、DbUnit等整合在一塊兒,使得DAO層的單元測試變得很是容易。Unitils採用模 塊化方式來整合第三方框架,經過實現擴展模塊接口org.unitils.core.Module來實現擴展功能。在Unitils中已經實現一個 DbUnitModule,很好整合了DbUnit。經過這個擴展模塊,就能夠在Unitils中使用Dbunit強大的數據集功能,如用於準備數據的 @DataSet註解、用於驗證數據的@ExpectedDataSet註解。Unitils集成DbUnit流程圖如圖16-5所示。



16.4.5  自定義擴展模塊

   Unitils經過模塊化的方式來組織各個功能模塊,對外提供一個統一的擴展模塊接口org.unitils.core.Module來實現與第三方框架 的集成及自定義擴展。在Unitils中已經實現目前一些主流框架的模塊擴展,如Spring、Hibernate、DbUnit、Testng等。若是 這些內置的擴展模塊沒法知足需求,咱們能夠實現本身的一些擴展模塊。擴展Unitils模塊很簡單,如代碼清單16-19所示。

Java代碼   收藏代碼
  1. package sample.unitils.module;  
  2. import java.lang.reflect.Method;  
  3. import org.unitils.core.TestListener;  
  4. import org.unitils.core. Module;  
  5. //① 實現Module接口  
  6. public class CustomExtModule implements Module {  
  7.      //② 實現獲取測試監聽的方法  
  8.     public TestListener getTestListener() {  
  9.         return new CustomExtListener();  
  10.     }  
  11.       
  12.     //② 新建監聽模塊   
  13.     protected class CustomExtListener extends TestListener {  
  14.          //③ 重寫 TestListener裏的相關方法,完成相關擴展的功能  
  15.         @Override  
  16.         public void afterTestMethod(Object testObject, Method testMethod,  
  17.                 Throwable testThrowable) {  
  18. …  
  19.         }  
  20.   
  21.         @Override  
  22.         public void beforeTestMethod(Object testObject, Method testMethod) {  
  23. …  
  24.         }  
  25.     }  
  26. …  
  27. }  


在①處新建自定義擴展模塊CustomExtModule,實現Module接口。在②處新建自定義監聽模塊,繼承TestListener。在 ③處重寫(@Override)TestListener裏的相關方法,完成相關擴展的功能。實現自定義擴展模塊以後,剩下的工做就是在Unitils配 置文件unitils.properties中註冊這個自定義擴展的模塊:

引用
unitils.modules=…,custom
unitils.module. custom.className= sample.unitils.module.CustomExtModule

http://stamen.iteye.com/blog/1480316

相關文章
相關標籤/搜索