Junit使用教程(四)

一、會用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補充就說到這裏了,希望大家多多分享經驗哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章