整合測試——Spring TestContext框架支援詳細解說

TestingGDR發表於2018-11-05

概述

    整合測試是在單元測試之上,通常是將一個或多個已進行過單元測試的元件組合起來完成的,即整合測試中一般不會出現Mock物件,都是實實在在的真實實現。

    對於單元測試,如前邊在進行資料訪問層單元測試時,通過Mock HibernateTemplate物件然後將其注入到相應的DAO實現,此時單元測試只測試某層的某個功能是否正確,對其他層如何提供服務採用Mock方式提供。

    對於整合測試,如要進行資料訪問層整合測試時,需要實實在在的HibernateTemplate物件然後將其注入到相應的DAO實現,此時整合測試將不僅測試該層功能是否正確,還將測試服務提供者提供的服務是否正確執行。

    使用Spring的一個好處是能非常簡單的進行整合測試,無需依賴web伺服器或應用伺服器即可完成測試。Spring通過提供一套TestContext框架來簡化整合測試,使用TestContext測試框架能獲得許多好處,如Spring IoC容器快取、事務管理、依賴注入、Spring測試支援類等等。

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})指定。

在這裡向大家推薦一個軟體測試學習交流群:175317069

準備整合測試環境

    對於整合測試環境各種配置應該和開發環境或實際生產環境配置相分離,即整合測試時應該使用單獨搭建一套獨立的測試環境,不應使用開發環境或實際生產環境的配置,從而保證測試環境、開發、生產環境相分離。

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關閉時刪除這些表結構。

到此我們測試環境修改完畢,在進行整合測試時一定要保證測試環境、開發環境、實際生產環境相分離,即對於不同的環境使用不同的配置檔案。

資料訪問層

資料訪問層整合測試,同單元測試一樣目的不僅測試該層定義的介面實現方法的行為是否正確,而且還要測試是否正確與資料庫互動,是否傳送並執行了正確的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指定的預設事務回滾行為,即將在測試方法執行完畢時回滾事務。

資料訪問層的整合測試也是非常簡單,與資料訪問層的單元測試類似,也應該只對複雜的資料訪問層程式碼進行測試。

業務邏輯層

業務邏輯層整合測試,目的同樣是測試該層的業務邏輯是否正確,對於資料訪問層實現通過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());
}
在這裡向大家推薦一個軟體測試學習交流群:175317069

由於我們新增了指定商品的Code碼因此購買將成功,如果失敗說明業務寫錯了,應該重寫。

業務邏輯層的整合測試也是非常簡單,與業務邏輯層的單元測試類似,也應該只對複雜的業務邏輯層程式碼進行測試。

表現層

對於表現層整合測試,同樣類似於單元測試,但對於業務邏輯層都將使用真實的實現,而不再是通過Mock物件來測試,這也是整合測試和單元測試的區別。

接下來讓我們學習一下如何進行表現層Action整合測試:

1、準備Struts提供的junit外掛, 到struts-2.2.1.1.zip中拷貝如下jar包到類路徑:

   
 

lib\struts2-junit-plugin-2.2.1.1.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.1、在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官網學習最新的測試技巧。

結語

 最後跟大家推薦一個學習資料分享群:175317069,裡面大牛已經為我們整理好了許多的學習資料,有自動化,介面,效能等等的學習資料!

人生是一個逆水行舟的過程,不進則退,我們們一起加油吧!

 

 

相關文章