作者:江南白衣  

     注重實效的TDD的確能加快,而不是拖慢開發(fā)的進度(片面的追求覆蓋率的全面UnitTest不在此列)
      一,可以實現(xiàn)真正分層開發(fā)。
      二,不需要依賴和頻繁重啟Web Container。
      三,手工測試總不免改動數(shù)據(jù)庫,如何把數(shù)據(jù)庫恢復(fù)到測試前的狀態(tài)是件傷腦筋的事情。而Unit Test可以使用自動Rollback機制,巧妙的解決了這件事情。

      Spring 下的Unit Test主要關(guān)注三個方面:
       1. bean的依賴注入
       2. 事務(wù)控制,Open Session in Test 及默認(rèn)回滾
       3. 脫離WebContainer對控制層的測試
      

   1.bean的依賴注入 
  能不依靠WebContainer來完成ApplicationContext的建立與POJO的依賴注入一向是Spring的得意之處。

String[] paths = { "classpath:applicationContext*.xml" };
ApplicationContext ctx 
=new ClassPathXmlApplicationContext(paths);
UserDAO dao 
= (UserDAO) ctx.getBean("userDAO");

    如果你連這也覺得麻煩,那么只要你的testCase繼承于Spring-mock.jar里的AbstractDependencyInjectionSpringContextTests,實現(xiàn)public String[] getConfigLocations()函數(shù), 并顯式寫一些需要注入的變量的setter函數(shù)。
    注:因為是AutoWire的,變量名必須等于Spring  context文件里bean的id。

2.Open Session in Test 及自動Rollback
   
又是來自Spring這個神奇國度的東西,加入下面幾句,就可以做到Open Session in Test ,解決Hibernate的lazy-load問題;而且接管原來的DAO里的事務(wù)控制定義,隨意定義測試結(jié)束時是提交還是回滾,如果默認(rèn)為回滾,則測試產(chǎn)生數(shù)據(jù)變動不會影響數(shù)據(jù)庫內(nèi)數(shù)據(jù)。
     你可以讓testCase繼承于AbstractTransactionalDataSourceSpringContextTests,通過setDefaultRollback(boolean)方法控制最后回滾還是提交。
 
    如果自己編寫,代碼是這樣的:

   protected PlatformTransactionManager transactionManager;
   
protected TransactionStatus transactionStatus;
   
protected boolean defaultRollback = true;
   
public void setUp()
   {
        transactionManager 
= (PlatformTransactionManager) ctx.getBean("transactionManager");
        transactionStatus 
= transactionManager.getTransaction(new DefaultTransactionDefinition());
   }
   
public void tearDown()
   {
        
if (defaultRollback)
            transactionManager.rollback(
this.transactionStatus);
        
else
           transactionManager.commit(
this.transactionStatus);
    }

  (注,hibernate太奸詐了,如果全部默認(rèn)回滾,只會在session里干活,一點不寫數(shù)據(jù)庫,達不到完全的測試效果。)

3.Controller層的Unit Test

controller層靠Spring提供的MockHttpServletRequest和Response來模擬真實的servlet環(huán)境,并且spring 2.0了加了一個AbstractModelAndViewTests,提供一些檢測返回值的utils函數(shù)。

    protected XmlWebApplicationContext ctx;
    
protected MockHttpServletRequest request = new MockHttpServletRequest("GET""");
    
protected MockHttpServletResponse response = new MockHttpServletResponse();
    
protected Controller controller = null;
    
protected ModelAndView mv = null;
   
public void setUp()
   {
        String[] paths 
= {"applicationContext*.xml","myappfuse-servlet.xml"};
        ctx 
= new XmlWebApplicationContext();
        ctx.setConfigLocations(paths);
        ctx.setServletContext(
new MockServletContext(""));        ctx.refresh();
        controller 
= (CustomerController) ctx.getBean("customerController");
        
//再加上前文的事務(wù)控制的代碼
   }
    
public void testCustomerList() throws Exception
    {
        request.setRequestURI(
"/customer.do");
        request.addParameter(
"action""listView");
        mv 
= controller.handleRequest(request, response);
        assertModelAttributeAvailable(mv, "customers");

    }

  
4.進一步簡化
一來這兩個基類的名字都太長了。
二來有一些公共的context文件的定義。

所以可以再抽象了幾個基類,分別是DAOTestCase,ControllerTestCase。

5. EasyMock
   MockObject是一樣徹底分層開發(fā)的好東西,而且使用上沒什么難度。而且已不再存在只支持接口不支持Class的限制。
  

//設(shè)定BookManager MockObject
        bookManagerMockControl = MockClassControl.createControl(BookManager.class);
        bookManagerMock 
= (BookManager) bookManagerMockControl.getMock();
        controller.setBookManager(bookManagerMock);
        
//錄制getAllBook()和getCategorys方法的期望值
        bookManagerMock.getAllBook();
        bookManagerMockControl.setReturnValue(
new ArrayList());
        bookManagerMockControl.replay();
         
//執(zhí)行操作
        mv = controller.handleRequest(request, response);
//驗證結(jié)果          
        assertModelAttributeAvailable(mv, "books");

   Easy Mock VS JMock:

    JMock 要求TestCase繼承于MockObjectTestCase太霸道了。妨礙了我繼承于Spring2.0的ModelAndViewTestCase和使用MockDao,RealDao并行的繼承體系。因此采用沒那么霸道的easyMock。

   另外,easyMock的腳本錄制雖不如jmock那么優(yōu)美,但勝在簡短易讀。jmock那句太長了 。


6. 顯示層測試
還有,顯示層至今沒有什么好的UnitTest方法,無論是不成才的httpUnit們還是笨重的GUI test工具。Appfuse一直用的那個ThoughtWork那個SeleniumJ3Unit的效果不知如何, 其中J3Unit號稱支持prototype。