一、會用Spring測試套件的好處
在開發基於Spring的應用時,如果你還直接使用Junit進行單元測試,那你就錯過了Spring為我們所提供的饕餮大餐了。使用Junit直接進行單元測試有以下四大不足:
1)導致多次Spring容器初始化問題
根據JUnit測試方法的調用流程,每執行一個測試方法都會建立一個測試案例的執行個體並調用setUp()方法。由於一般情況下,我們在setUp()方法中初始化Spring容器,這意味著如果測試案例有多少個測試方法,Spring容器就會被重複初始化多次。雖然初始化Spring容器的速度並不會太慢,但由於可能會在Spring容器初始化時執行載入Hibernate對應檔等耗時的操作,如果每執行一個測試方法都必須重複初始化Spring容器,則對測試效能的影響是不容忽視的;
使用Spring測試套件,Spring容器只會初始化一次
2)需要使用寫入程式碼方式手工擷取Bean
在測試案例類中我們需要通過ctx.getBean()方法從Spirng容器中擷取需要測試的目標Bean,並且還要進行強制類型轉換的造型操作。這種乏味的操作迷漫在測試案例的代碼中,讓人覺得煩瑣不堪;
使用Spring測試套件,測試案例類中的屬性會被自動填滿Spring容器的對應Bean,無須在手工設定Bean!
3)資料庫現場容易遭受破壞
測試方法對資料庫的更改操作會持久化到資料庫中。雖然是針對開發資料庫進行操作,但如果資料操作的影響是持久的,可能會影響到後面的測試行為。舉個例子,使用者在測試方法中插入一條ID為1的User記錄,第一次運行不會有問題,第二次運行時,就會因為主鍵衝突而導致測試案例失敗。所以應該既能夠完成功能邏輯檢查,又能夠在測試完成後恢複現場,不會留下“後遺症”;
使用Spring測試套件,Spring會在你驗證後,自動復原對資料庫的操作,保證資料庫的現場不被破壞,因此重複測試不會發生問題!
4)不方便對資料操作正確性進行檢查
假如我們向登入日誌表插入了一條成功登入日誌,可是我們卻沒有對t_login_log表中是否確實添加了一條記錄進行檢查。一般情況下,我們可能是開啟資料庫,肉眼觀察是否插入了相應的記錄,但這嚴重違背了自動化的測試的原則。試想在測試包括成千上萬個資料操作行為的程式時,如何用肉眼進行檢查?
只要你繼承Spring的測試套件的用例類,你就可以通過jdbcTemplate(或Dao等)在同一事務中訪問資料庫,查詢資料的變化,驗證操作的正確性!
Spring提供了一套擴充於Junit測試案例的測試套件,使用這套測試套件完全解決了以上四個問題,讓我們測試Spring的應用更加方便。這個測試套件主要由org.springframework.test包下的若干類組成,使用簡單快捷,方便上手。
二、使用方法
1)基本用法
package com.test;import javax.annotation.Resource;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })public class UserServiceTest {@Resourceprivate IUserService userService;@Testpublic void testAddOpinion1() {userService.downloadCount(1);System.out.println(1);}@Testpublic void testAddOpinion2() {userService.downloadCount(2);System.out.println(2);}}
@RunWith(SpringJUnit4ClassRunner.class) 用於配置spring中測試的環境
@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })用於指定設定檔所在的位置
@Resource注入Spring容器Bean對象,注意與@Autowired區別
2)事務用法
package com.test;import javax.annotation.Resource;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.annotation.Rollback;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import org.springframework.test.context.transaction.TransactionConfiguration;import org.springframework.transaction.annotation.Transactional;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })@Transactional@TransactionConfiguration(transactionManager = "transactionManager")//@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)public class UserServiceTest {@Resourceprivate IUserService userService;@Test//@Transactionalpublic void testAddOpinion1() {userService.downloadCount(1);System.out.println(1);}@Test@Rollback(false)public void testAddOpinion2() {userService.downloadCount(2);System.out.println(2);}}
@TransactionConfiguration(transactionManager="transactionManager")讀取Spring設定檔中名為transactionManager的事務配置,defaultRollback為交易回復預設設定。該註解是可選的,可使用@Transactional與@Rollback配合完成交易管理。當然也可以使用@Transactional與@TransactionConfiguration配合。
@Transactional開啟事務。可放到類或方法上,類上作用於所有方法。
@Rollback交易回復配置。只能放到方法上。
3)繼承AbstractTransactionalJUnit4SpringContextTests
package com.test;import javax.annotation.Resource;import org.junit.Test;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;import org.springframework.test.context.transaction.TransactionConfiguration;@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)public class UserServiceTest extends AbstractTransactionalJUnit4SpringContextTests {@Resourceprivate IUserService userService;@Testpublic void testAddOpinion1() {userService.downloadCount(1);System.out.println(1);}@Testpublic void testAddOpinion2() {userService.downloadCount(2);System.out.println(2);}}
AbstractTransactionalJUnit4SpringContextTests:這個類為我們解決了在web.xml中配置OpenSessionInview所解決的session生命週期延長的問題,所以要繼承這個類。該類已經在類層級預先配置了好了事物支援,因此不必再配置@Transactional和@RunWith
4)繼承
package com.test;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;import org.springframework.test.context.transaction.TransactionConfiguration;@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })@TransactionConfiguration(transactionManager = "transactionManager")public class BaseTestCase extends AbstractTransactionalJUnit4SpringContextTests {}
package com.test;import javax.annotation.Resource;import org.junit.Test;import org.springframework.test.annotation.Rollback;public class UserServiceTest extends BaseTestCase {@Resourceprivate IUserService userService;@Testpublic void testAddOpinion1() {userService.downloadCount(1);System.out.println(1);}@Test@Rollback(false)public void testAddOpinion2() {userService.downloadCount(2);System.out.println(2);}}
5)綜合
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration@TransactionConfiguration@Transactionalpublic class PersonDaoTransactionUnitTest extends AbstractTransactionalJUnit4SpringContextTests {final Logger logger = LoggerFactory.getLogger(PersonDaoTransactionUnitTest.class);protected static int SIZE = 2;protected static Integer ID = new Integer(1);protected static String FIRST_NAME = "Joe";protected static String LAST_NAME = "Smith";protected static String CHANGED_LAST_NAME = "Jackson";@Autowiredprotected PersonDao personDao = null;/** * Tests that the size and first record match what is expected before the transaction. */@BeforeTransactionpublic void beforeTransaction() {testPerson(true, LAST_NAME);}/** * Tests person table and changes the first records last name. */@Testpublic void testHibernateTemplate() throws SQLException {assertNotNull("Person DAO is null.", personDao);Collection<Person> lPersons = personDao.findPersons();assertNotNull("Person list is null.", lPersons);assertEquals("Number of persons should be " + SIZE + ".", SIZE, lPersons.size());for (Person person : lPersons) {assertNotNull("Person is null.", person);if (ID.equals(person.getId())) {assertEquals("Person first name should be " + FIRST_NAME + ".", FIRST_NAME, person.getFirstName());assertEquals("Person last name should be " + LAST_NAME + ".", LAST_NAME, person.getLastName());person.setLastName(CHANGED_LAST_NAME);personDao.save(person);}}}/** * Tests that the size and first record match what is expected after the transaction. */@AfterTransactionpublic void afterTransaction() {testPerson(false, LAST_NAME);}/** * Tests person table. */protected void testPerson(boolean beforeTransaction, String matchLastName) {List<Map<String, Object>> lPersonMaps = simpleJdbcTemplate.queryForList("SELECT * FROM PERSON");assertNotNull("Person list is null.", lPersonMaps);assertEquals("Number of persons should be " + SIZE + ".", SIZE, lPersonMaps.size());Map<String, Object> hPerson = lPersonMaps.get(0);logger.debug((beforeTransaction ? "Before" : "After") + " transaction. " + hPerson.toString());Integer id = (Integer) hPerson.get("ID");String firstName = (String) hPerson.get("FIRST_NAME");String lastName = (String) hPerson.get("LAST_NAME");if (ID.equals(id)) {assertEquals("Person first name should be " + FIRST_NAME + ".", FIRST_NAME, firstName);assertEquals("Person last name should be " + matchLastName + ".", matchLastName, lastName);}}}
@BeforeTransaction在事務之前執行
@AfterTransaction在事務之後執行
@NotTransactional不開啟事務
好了,本篇作為Junit補充就說到這裡了,希望大家多多分享經驗哦。