一、會用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 {
@Resource
private IUserService userService;
@Test
public void testAddOpinion1() {
userService.downloadCount(1);
System.out.println(1);
}
@Test
public 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 {
@Resource
private IUserService userService;
@Test
// @Transactional
public 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 {
@Resource
private IUserService userService;
@Test
public void testAddOpinion1() {
userService.downloadCount(1);
System.out.println(1);
}
@Test
public 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 {
@Resource
private IUserService userService;
@Test
public 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
@Transactional
public 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";
@Autowired
protected PersonDao personDao = null;
/**
* Tests that the size and first record match what is expected before the transaction.
*/
@BeforeTransaction
public void beforeTransaction() {
testPerson(true, LAST_NAME);
}
/**
* Tests person table and changes the first records last name.
*/
@Test
public 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.
*/
@AfterTransaction
public 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補充就說到這裏了,希望大家多多分享經驗哦。