13.3 集成測試
13.3.1 概述
集成測試是在單元測試之上,通常是將一個或多個已進行過單元測試的組件組合起來完成的,即集成測試中一般不會出現Mock對象,都是實實在在的真實實現。
對於單元測試,如前邊在進行數據訪問層單元測試時,通過Mock HibernateTemplate對象然後將其注入到相應的DAO實現,此時單元測試只測試某層的某個功能是否正確,對其他層如何提供服務採用Mock方式提供。
對於集成測試,如要進行數據訪問層集成測試時,需要實實在在的HibernateTemplate對象然後將其注入到相應的DAO實現,此時集成測試將不僅測試該層功能是否正確,還將測試服務提供者提供的服務是否正確執行。
使用Spring的一個好處是能非常簡單的進行集成測試,無需依賴web服務器或應用服務器即可完成測試。Spring通過提供一套TestContext框架來簡化集成測試,使用TestContext測試框架能獲得許多好處,如Spring IoC容器緩存、事務管理、依賴注入、Spring測試支持類等等。
13.3.2 Spring TestContext框架支持
Spring TestContext框架提供了一些通用的集成測試支持,主要提供如下支持:
一、上下文管理及緩存:
對於每一個測試用例(測試類)應該只有一個上下文,而不是每個測試方法都創建新的上下文,這樣有助於減少啓動容器的開銷,提供測試效率。可通過如下方式指定要加載的上下文:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(
- locations={"classpath:applicationContext-resources-test.xml",
- "classpath:cn/javass/point/dao/applicationContext-hibernate.xml"})
- public class GoodsHibernateDaoIntegrationTest {
- }
- locations:指定Spring配置文件位置;
- inheritLocations:如果設置爲false,將屏蔽掉父類中使用該註解指定的配置文件位置,默認爲true表示繼承父類中使用該註解指定的配置文件位置。
二、Test Fixture(測試固件)的依賴注入:
Test Fixture可以指運行測試時需要的任何東西,一般通過@Before定義的初始化Fixture方法準備這些資源,而通過@After定義的銷燬Fixture方法銷燬或還原這些資源。
Test Fixture的依賴注入就是使用Spring IoC容器的注入功能準備和銷燬這些資源。可通過如下方式注入Test Fixture:
- @Autowired
- private IGoodsDao goodsDao;
- @Autowired
- private ApplicationContext ctx;
即可以通過Spring提供的註解實現Bean的依賴注入來完成Test Fixture的依賴注入。
三、事務管理:
開啓測試類的事務管理支持,即使用Spring 容器的事務管理功能,從而可以獨立於應用服務器完成事務相關功能的測試。爲了使測試中的事務管理起作用需要通過如下方式開啓測試類事務的支持:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(
- locations={"classpath:applicationContext-resources-test.xml",
- "classpath:cn/javass/point/dao/applicationContext-hibernate.xml"})
- @TransactionConfiguration(
- transactionManager = "txManager", defaultRollback=true)
- public class GoodsHibernateDaoIntegrationTest {
- }
Spring提供如下事務相關注解來支持事務管理:
- @Transactional:使用@Transactional註解的類或方法將得到事務支持
- transactionManager:指定事務管理器;
- defaultRollback:是否回滾事務,默認爲true表示回滾事務。
Spring還通過提供如下註解來簡化事務測試:
- @Transactional:使用@Transactional註解的類或方法表示需要事務支持;
- @NotTransactional:只能註解方法,使用@NotTransactional註解的方法表示不需要事務支持,即不運行在事務中,Spring 3開始已不推薦使用;
- @BeforeTransaction和@AfterTransaction:使用這兩個註解註解的方法定義了在一個事務性測試方法之前或之後執行的行爲,且被註解的方法將運行在該事務性方法的事務之外。
- @Rollback(true):默認爲true,用於替換@TransactionConfiguration中定義的defaultRollback指定的回滾行爲。
四、常用註解支持:Spring框架提供如下註解來簡化集成測試:
- @DirtiesContext:表示每個測試方法執行完畢需關閉當前上下文並重建一個全新的上下文,即不緩存上下文。可應用到類或方法級別,但在JUnit 3.8中只能應用到方法級別。
- @ExpectedException:表示被註解的方法預期將拋出一個異常,使用如@ExpectedException(NotCodeException.class)來指定異常,定義方式類似於Junit 4中的@Test(expected = NotCodeException.class),@ExpectedException註解和@Test(expected =……)應該兩者選一。
- @Repeat:表示被註解的方法應被重複執行多少次,使用如@Repeat(2)方式指定。
- @Timed:表示被註解的方法必須在多長時間內運行完畢,超時將拋出異常,使用如@Timed(millis=10)方式指定,單位爲毫秒。注意此處指定的時間是如下方法執行時間之和:測試方法執行時間(或者任何測試方法重複執行時間之和)、@Before和@After註解的測試方法之前和之後執行的方法執行時間。而Junit 4中的@Test(timeout=2)指定的超時時間只是測試方法執行時間,不包括任何重複等。
- 除了支持如上註解外,還支持【第十二章 零配置】中依賴注入等註解。
五、TestContext框架支持類:提供對測試框架的支持,如Junit、TestNG測試框架,用於集成Spring TestContext和測試框架來簡化測試,TestContext框架提供如下支持類:
- JUnit 3.8支持類:提供對Spring TestContext框架與Junit3.8測試框架的集成:
AbstractJUnit38SpringContextTests:我們的測試類繼承該類後將獲取到Test Fixture的依賴注入好處。
AbstractTransactionalJUnit38SpringContextTests:我們的測試類繼承該類後除了能得到Test Fixture的依賴注入好處,還額外獲取到事務管理支持。
- JUnit 4.5+支持類:提供對Spring TestContext框架與Junit4.5+測試框架的集成:
AbstractJUnit4SpringContextTests:我們的測試類繼承該類後將獲取到Test Fixture的依賴注入好處。
AbstractTransactionalJUnit4SpringContextTests:我們的測試類繼承該類後除了能得到Test Fixture的依賴注入好處,還額外獲取到事務管理支持。
- 定製 Junit4.5+運行器:通過定製自己的Junit4.5+運行器從而無需繼承JUnit 4.5+支持類即可完成需要的功能,如Test Fixture的依賴注入、事務管理支持,
@RunWith(SpringJUnit4ClassRunner.class):使用該註解註解到測試類上表示將集成Spring TestContext和Junit 4.5+測試框架。
@TestExecutionListeners:該註解用於指定TestContext框架的監聽器用於與TestContext框架管理器發佈的測試執行事件進行交互,TestContext框架提供如下三個默認的監聽器:DependencyInjectionTestExecutionListener、DirtiesContextTestExecutionListener、TransactionalTestExecutionListener分別完成對Test Fixture的依賴注入、@DirtiesContext支持和事務管理支持,即在默認情況下將自動註冊這三個監聽器,另外還可以使用如下方式指定監聽器:
- @RunWith(SpringJUnit4ClassRunner.class)
- @TestExecutionListeners({})
- public class GoodsHibernateDaoIntegrationTest {
- }
如上配置將通過定製的Junit4.5+運行器運行,但不會完成Test Fixture的依賴注入、事務管理等等,如果只需要Test Fixture的依賴注入,可以使用@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})指定。
- TestNG支持類:提供對Spring TestContext框架與TestNG測試框架的集成:
AbstractTestNGSpringContextTests:我們的測試類繼承該類後將獲取到Test Fixture的依賴注入好處。
AbstractTransactionalTestNGSpringContextTests:我們的測試類繼承該類後除了能得到Test Fixture的依賴注入好處,還額外獲取到事務管理支持。
到此Spring TestContext測試框架減少完畢了,接下來讓我們學習一下如何進行集成測試吧。
13.3.3 準備集成測試環境
對於集成測試環境各種配置應該和開發環境或實際生產環境配置相分離,即集成測試時應該使用單獨搭建一套獨立的測試環境,不應使用開發環境或實際生產環境的配置,從而保證測試環境、開發、生產環境相分離。
1、拷貝一份Spring資源配置文件applicationContext-resources.xml,並命名爲applicationContext-resources-test.xml表示用於集成測試使用,並修改如下內容:
- <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath:resources-test.properties</value>
- </list>
- </property>
- </bean>
2、拷貝一份替換配置元數據的資源文件(resources/resources.properties),並命名爲resources-test.properties表示用於集成測試使用,並修改爲以下內容:
- db.driver.class=org.hsqldb.jdbcDriver
- db.url=jdbc:hsqldb:mem:point_shop
- db.username=sa
- db.password=
- #Hibernate屬性
- hibernate.dialect=org.hibernate.dialect.HSQLDialect
- hibernate.hbm2ddl.auto=create-drop
- hibernate.show_sql=false
- hibernate.format_sql=true
- jdbc:hsqldb:mem:point_shop:我們在集成測試時將使用HSQLDB,並採用內存數據庫模式運行;
- hibernate.hbm2ddl.auto=create-drop:表示在創建SessionFactory時根據Hibernate映射配置創建相應Model的表結構,並在SessionFactory關閉時刪除這些表結構。
到此我們測試環境修改完畢,在進行集成測試時一定要保證測試環境、開發環境、實際生產環境相分離,即對於不同的環境使用不同的配置文件。
13.3.4 數據訪問層
數據訪問層集成測試,同單元測試一樣目的不僅測試該層定義的接口實現方法的行爲是否正確,而且還要測試是否正確與數據庫交互,是否發送並執行了正確的SQL,SQL執行成功後是否正確的組裝了業務邏輯層需要的數據。
數據訪問層集成測試不再通過Mock對象與數據庫交互的API來完成測試,而是使用實實在在存在的與數據庫交互的對象來完成測試。
接下來讓我們學習一下如何進行數據訪問層集成測試:
1、在test文件夾下創建如下測試類:
- package cn.javass.point.dao.hibernate;
- //省略import
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(
- locations={"classpath:applicationContext-resources-test.xml",
- "classpath:cn/javass/point/dao/applicationContext-hibernate.xml"})
- @TransactionConfiguration(transactionManager = "txManager", defaultRollback=false)
- public class GoodsHibernateDaoIntegrationTest {
- @Autowired
- private ApplicationContext ctx;
- @Autowired
- private IGoodsCodeDao goodsCodeDao;
- }
- @RunWith(SpringJUnit4ClassRunner.class):表示使用自己定製的Junit4.5+運行器來運行測試,即完成Spring TestContext框架與Junit集成;
- @ContextConfiguration:指定要加載的Spring配置文件,此處注意我們的Spring資源配置文件爲“applicationContext-resources-test.xml”;
- @TransactionConfiguration:開啓測試類的事務管理支持配置,並指定事務管理器和默認回滾行爲;
- @Autowired:完成Test Fixture(測試固件)的依賴注入。
2、測試支持寫完後,接下來測試一下分頁查詢所有已發佈的商品是否滿足需求:
- @Transactional
- @Rollback
- @Test
- public void testListAllPublishedSuccess() {
- GoodsModel goods = new GoodsModel();
- goods.setDeleted(false);
- goods.setDescription("");
- goods.setName("測試商品");
- goods.setPublished(true);
- goodsDao.save(goods);
- Assert.assertTrue(goodsDao.listAllPublished(1).size() == 1);
- Assert.assertTrue(goodsDao.listAllPublished(2).size() == 0);
- }
- @Transactional:表示測試方法將允許在事務環境;
- @Rollback:表示替換@ContextConfiguration指定的默認事務回滾行爲,即將在測試方法執行完畢時回滾事務。
數據訪問層的集成測試也是非常簡單,與數據訪問層的單元測試類似,也應該只對複雜的數據訪問層代碼進行測試。
13.3.5 業務邏輯層
業務邏輯層集成測試,目的同樣是測試該層的業務邏輯是否正確,對於數據訪問層實現通過Spring IoC容器完成裝配,即使用真實的數據訪問層實現來獲取相應的底層數據。
接下來讓我們學習一下如何進行業務邏輯層集成測試:
1、在test文件夾下創建如下測試類:
- @ContextConfiguration(
- locations={"classpath:applicationContext-resources-test.xml",
- "classpath:cn/javass/point/dao/applicationContext-hibernate.xml",
- "classpath:cn/javass/point/service/applicationContext-service.xml"})
- @TransactionConfiguration(transactionManager = "txManager", defaultRollback=false)
- public class GoodsCodeServiceImplIntegrationTest extends AbstractJUnit4SpringContextTests {
- @Autowired
- private IGoodsCodeService goodsCodeService;
- @Autowired
- private IGoodsService goodsService;
- }
- AbstractJUnit4SpringContextTests:表示將Spring TestContext框架與Junit4.5+測試框架集成;
- @ContextConfiguration:指定要加載的Spring配置文件,此處注意我們的Spring資源配置文件爲“applicationContext-resources-test.xml”;
- @TransactionConfiguration:開啓測試類的事務管理支持配置,並指定事務管理器和默認回滾行爲;
- @Autowired:完成Test Fixture(測試固件)的依賴注入。
2、測試支持寫完後,接下來測試一下購買商品Code碼是否滿足需求:
2.1、測試購買失敗的場景:
- @Transactional
- @Rollback
- @ExpectedException(NotCodeException.class)
- @Test
- public void testBuyFail() {
- goodsCodeService.buy("test", 1);
- }
|
由於我們數據庫中沒有相應商品的Code碼,因此將拋出NotCodeException異常。
2.2、測試購買成功的場景:
- @Transactional
- @Rollback
- @Test
- public void testBuySuccess() {
- //1.添加商品
- GoodsModel goods = new GoodsModel();
- goods.setDeleted(false);
- goods.setDescription("");
- goods.setName("測試商品");
- goods.setPublished(true);
- goodsService.save(goods);
- //2.添加商品Code碼
- GoodsCodeModel goodsCode = new GoodsCodeModel();
- goodsCode.setGoods(goods);
- goodsCode.setCode("test");
- goodsCodeService.save(goodsCode);
- //3.測試購買商品Code碼
- GoodsCodeModel resultGoodsCode = goodsCodeService.buy("test", 1);
- Assert.assertEquals(goodsCode.getId(), resultGoodsCode.getId());
- }
|
由於我們添加了指定商品的Code碼因此購買將成功,如果失敗說明業務寫錯了,應該重寫。
業務邏輯層的集成測試也是非常簡單,與業務邏輯層的單元測試類似,也應該只對複雜的業務邏輯層代碼進行測試。
13.3.5 表現層
對於表現層集成測試,同樣類似於單元測試,但對於業務邏輯層都將使用真實的實現,而不再是通過Mock對象來測試,這也是集成測試和單元測試的區別。
接下來讓我們學習一下如何進行表現層Action集成測試:
1、準備Struts提供的junit插件, 到struts-2.2.1.1.zip中拷貝如下jar包到類路徑:
|
2、測試支持類:Struts2提供StrutsSpringTestCase測試支持類,我們所有的Action測試類都需要繼承該類;
3、準備Spring配置文件:由於我們的測試類繼承StrutsSpringTestCase且將通過覆蓋該類的getContextLocations方法來指定Spring配置文件,但由於getContextLocations方法只能返回一個配置文件,因此我們需要新建一個用於導入其他Spring配置文件的配置文件applicationContext-test.xml,具體內容如下:
- <import resource="classpath:applicationContext-resources-test.xml"/>
- <import resource="classpath:cn/javass/point/dao/applicationContext-hibernate.xml"/>
- <import resource="classpath:cn/javass/point/service/applicationContext-service.xml"/>
- <import resource="classpath:cn/javass/point/web/pointShop-admin-servlet.xml"/>
- <import resource="classpath:cn/javass/point/web/pointShop-front-servlet.xml"/>
3、在test文件夾下創建如下測試類:
- package cn.javass.point.web.front;
- //省略import
- @RunWith(SpringJUnit4ClassRunner.class)
- @TestExecutionListeners({})
- public class GoodsActionIntegrationTest extends StrutsSpringTestCase {
- @Override
- protected String getContextLocations() {
- return "classpath:applicationContext-test.xml";
- }
- @Before
- public void setUp() throws Exception {
- //1 指定Struts2配置文件
- //該方式等價於通過web.xml中的<init-param>方式指定參數
- Map<String, String> dispatcherInitParams = new HashMap<String, String>();
- ReflectionTestUtils.setField(this, "dispatcherInitParams", dispatcherInitParams);
- //1.1 指定Struts配置文件位置
- dispatcherInitParams.put("config", "struts-default.xml,struts-plugin.xml,struts.xml");
- super.setUp();
- }
- @After
- public void tearDown() throws Exception {
- super.tearDown();
- }
- }
- @RunWith(SpringJUnit4ClassRunner.class):表示使用自己定製的Junit4.5+運行器來運行測試,即完成Spring TestContext框架與Junit集成;
- @TestExecutionListeners({}):沒有指定任何監聽器,即不會自動完成對Test Fixture的依賴注入、@DirtiesContext支持和事務管理支持;
- StrutsSpringTestCase:集成測試Struts2+Spring時所有集成測試類必須繼承該類;
- setUp方法:在每個測試方法之前都執行的初始化方法,其中dispatcherInitParams用於指定等價於在web.xml中的<init-param>方式指定的參數;必須調用super.setUp()用於初始化Struts2和Spring環境。
- tearDown():在每個測試方法之前都執行的銷燬方法,必須調用super.tearDown()來銷燬Spring容器等。
4、測試支持寫完後,接下來測試一下前臺購買商品Code碼是否滿足需求:
4.1、測試購買失敗的場景:
- @Test
- public void testBuyFail() throws UnsupportedEncodingException, ServletException {
- //2 前臺購買商品失敗
- //2.1 首先重置hhtp相關對象,並準備準備請求參數
- initServletMockObjects();
- request.setParameter("goodsId", String.valueOf(Integer.MIN_VALUE));
- //2.2 調用前臺GoodsAction的buy方法完成購買相應商品的Code碼
- executeAction("/goods/buy.action");
- GoodsAction frontGoodsAction = (GoodsAction) ActionContext.getContext().getActionInvocation().getAction();
- //2.3 驗證前臺GoodsAction的buy方法有錯誤
- Assert.assertTrue(frontGoodsAction.getActionErrors().size() > 0);
- }
- initServletMockObjects():用於重置所有http相關對象,如request等;
- request.setParameter("goodsId", String.valueOf(Integer.MIN_VALUE)):用於準備請求參數;
- executeAction("/goods/buy.action"):通過模擬http請求來調用前臺GoodsAction的buy方法完成商品購買
- Assert.assertTrue(frontGoodsAction.getActionErrors().size() > 0):表示執行Action時有錯誤,即Action動作錯誤。如果條件不成立,說明我們Action功能是錯誤的,需要修改。
4.2、測試購買成功的場景:
- @Test
- public void testBuySuccess() throws UnsupportedEncodingException, ServletException {
- //3 後臺新增商品
- //3.1 準備請求參數
- request.setParameter("goods.name", "測試商品");
- request.setParameter("goods.description", "測試商品描述");
- request.setParameter("goods.originalPoint", "1");
- request.setParameter("goods.nowPoint", "2");
- request.setParameter("goods.published", "true");
- //3.2 調用後臺GoodsAction的add方法完成新增
- executeAction("/admin/goods/add.action");
- //2.3 獲取GoodsAction的goods屬性
- GoodsModel goods = (GoodsModel) findValueAfterExecute("goods");
- //4 後臺新增商品Code碼
- //4.1 首先重置hhtp相關對象,並準備準備請求參數
- initServletMockObjects();
- request.setParameter("goodsId", String.valueOf(goods.getId()));
- request.setParameter("codes", "a\rb");
- //4.2 調用後臺GoodsCodeAction的add方法完成新增商品Code碼
- executeAction("/admin/goodsCode/add.action");
- //5 前臺購買商品成功
- //5.1 首先重置hhtp相關對象,並準備準備請求參數
- initServletMockObjects();
- request.setParameter("goodsId", String.valueOf(goods.getId()));
- //5.2 調用前臺GoodsAction的buy方法完成購買相應商品的Code碼
- executeAction("/goods/buy.action");
- GoodsAction frontGoodsAction = (GoodsAction) ActionContext.getContext().getActionInvocation().getAction();
- //5.3 驗證前臺GoodsAction的buy方法沒有錯誤
- Assert.assertTrue(frontGoodsAction.getActionErrors().size() == 0);
- }
- executeAction("/admin/goods/add.action"):調用後臺GoodsAction的add方法,用於新增商品;
- executeAction("/admin/goodsCode/add.action"):調用後臺GoodCodeAction的add方法用於新增商品Code碼;
- executeAction("/goods/buy.action"):調用前臺GoodsAction的buy方法,用於購買相應商品,其中Assert.assertTrue(frontGoodsAction.getActionErrors().size() == 0)表示購買成功,即Action動作正確。
表現層Action集成測試介紹就到此爲止,如何深入StrutsSpringTestCase來完成集成測試已超出本書範圍,如果讀者對這部分感興趣可以到Struts2官網學習最新的測試技巧。
原創內容,轉載請註明私塾在線【http://sishuok.com/forum/blogPost/list/0/2557.html】