作者:江南白衣
注重實效的TDD的確能加快,而不是拖慢開發(fā)的進度(片面的追求覆蓋率的全面UnitTest不在此列)
一,可以實現(xiàn)真正分層開發(fā)。
二,不需要依賴和頻繁重啟Web Container。
三,手工測試總不免改動數(shù)據(jù)庫,如何把數(shù)據(jù)庫恢復到測試前的狀態(tài)是件傷腦筋的事情。而Unit Test可以使用自動Rollback機制,巧妙的解決了這件事情。
Spring 下的Unit Test主要關(guān)注三個方面:
1. bean的依賴注入
2. 事務控制,Open Session in Test 及默認回滾
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里的事務控制定義,隨意定義測試結(jié)束時是提交還是回滾,如果默認為回滾,則測試產(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太奸詐了,如果全部默認回滾,只會在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");
//
再加上前文的事務控制的代碼
}
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那個Selenium和J3Unit的效果不知如何, 其中J3Unit號稱支持prototype。