作者:江南白衣
注重實(shí)效的TDD的確能加快,而不是拖慢開(kāi)發(fā)的進(jìn)度(片面的追求覆蓋率的全面UnitTest不在此列)
一,可以實(shí)現(xiàn)真正分層開(kāi)發(fā)。
二,不需要依賴(lài)和頻繁重啟Web Container。
三,手工測(cè)試總不免改動(dòng)數(shù)據(jù)庫(kù),如何把數(shù)據(jù)庫(kù)恢復(fù)到測(cè)試前的狀態(tài)是件傷腦筋的事情。而Unit Test可以使用自動(dòng)Rollback機(jī)制,巧妙的解決了這件事情。
Spring 下的Unit Test主要關(guān)注三個(gè)方面:
1. bean的依賴(lài)注入
2. 事務(wù)控制,Open Session in Test 及默認(rèn)回滾
3. 脫離WebContainer對(duì)控制層的測(cè)試
1.bean的依賴(lài)注入
能不依靠WebContainer來(lái)完成ApplicationContext的建立與POJO的依賴(lài)注入一向是Spring的得意之處。
String[] paths = { "classpath:applicationContext*.xml" };
ApplicationContext ctx =new ClassPathXmlApplicationContext(paths);
UserDAO dao = (UserDAO) ctx.getBean("userDAO");
如果你連這也覺(jué)得麻煩,那么只要你的testCase繼承于Spring-mock.jar里的AbstractDependencyInjectionSpringContextTests,實(shí)現(xiàn)public String[] getConfigLocations()函數(shù), 并顯式寫(xiě)一些需要注入的變量的setter函數(shù)。
注:因?yàn)槭茿utoWire的,變量名必須等于Spring context文件里bean的id。
2.Open Session in Test 及自動(dòng)Rollback
又是來(lái)自Spring這個(gè)神奇國(guó)度的東西,加入下面幾句,就可以做到Open Session in Test ,解決Hibernate的lazy-load問(wèn)題;而且接管原來(lái)的DAO里的事務(wù)控制定義,隨意定義測(cè)試結(jié)束時(shí)是提交還是回滾,如果默認(rèn)為回滾,則測(cè)試產(chǎn)生數(shù)據(jù)變動(dòng)不會(huì)影響數(shù)據(jù)庫(kù)內(nèi)數(shù)據(jù)。
你可以讓testCase繼承于AbstractTransactionalDataSourceSpringContextTests,通過(guò)setDefaultRollback(boolean)方法控制最后回滾還是提交。
如果自己編寫(xiě),代碼是這樣的:
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)回滾,只會(huì)在session里干活,一點(diǎn)不寫(xiě)數(shù)據(jù)庫(kù),達(dá)不到完全的測(cè)試效果。)
3.Controller層的Unit Test
controller層靠Spring提供的MockHttpServletRequest和Response來(lái)模擬真實(shí)的servlet環(huán)境,并且spring 2.0了加了一個(gè)AbstractModelAndViewTests,提供一些檢測(cè)返回值的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.進(jìn)一步簡(jiǎn)化
一來(lái)這兩個(gè)基類(lèi)的名字都太長(zhǎng)了。
二來(lái)有一些公共的context文件的定義。
所以可以再抽象了幾個(gè)基類(lèi),分別是DAOTestCase,ControllerTestCase。
5. EasyMock
MockObject是一樣徹底分層開(kāi)發(fā)的好東西,而且使用上沒(méi)什么難度。而且已不再存在只支持接口不支持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);
//驗(yàn)證結(jié)果
assertModelAttributeAvailable(mv, "books");
Easy Mock VS JMock:
JMock 要求TestCase繼承于MockObjectTestCase太霸道了。妨礙了我繼承于Spring2.0的ModelAndViewTestCase和使用MockDao,RealDao并行的繼承體系。因此采用沒(méi)那么霸道的easyMock。
另外,easyMock的腳本錄制雖不如jmock那么優(yōu)美,但勝在簡(jiǎn)短易讀。jmock那句太長(zhǎng)了 。
6. 顯示層測(cè)試
還有,顯示層至今沒(méi)有什么好的UnitTest方法,無(wú)論是不成才的httpUnit們還是笨重的GUI test工具。Appfuse一直用的那個(gè)ThoughtWork那個(gè)Selenium和J3Unit的效果不知如何, 其中J3Unit號(hào)稱(chēng)支持prototype。