天天看點

Spring TestContext測試架構 直接使用Junit測試Spring程式存在的不足 Spring TestContext 測試架構體系結構 總結

直接使用Junit測試Spring程式存在的不足... 1

Spring TestContext 測試架構體系結構... 2

TestContext 核心類、支援類以及注解類... 2

TestContext. 2

TestContextManager. 2

TestExecutionListener. 2

@TestExecutionListeners. 3

@ContextConfiguration. 3

@RunWith. 3

SpringJUnit4ClassRunner. 3

ContextCache. 4

ContextLoader. 4

ApplicationContextAware. 4

AbstractJUnit4SpringContextTests. 4

AbstractTransactionalJUnit4SpringContextTests. 5

常用注解... 5

@IfProfileValue. 5

@ProfileValueSourceConfiguration. 5

@DirtiesContext. 6

@ExpectedException. 6

@Timed. 6

@Repeat. 7

@Rollback. 7

@Transactional7

@NotTransactional7

@Autowired. 8

@Resource. 8

@Qualifier. 9

@TransactionConfiguration. 9

@BeforeTransaction. 9

@AfterTransaction. 9

總結... 10

基于注解的TestContext測試架構,它采用注解技術可以讓POJO成為Spring的測試用例,可以運作在Junit3.8 Junit4.4 TestNG等測試架構之下

直接使用Junit測試Spring程式存在的不足

1.       導緻Spring容器多次初始化問題:

根據 JUnit 測試用例的調用流程,每執行一個測試方法都會重新建立一個測試用例執行個體并調用其 setUp() 方法。由于在一般情況下,我們都在 setUp() 方法中初始化 Spring 容器,這意味着測試用例中有多少個測試方法,Spring 容器就會被重複初始化多少次。

2.       需要使用寫死方式手工擷取Bean

在測試用例中,我們需要通過 ApplicationContext.getBean() 的方法從 Spirng 容器中擷取需要測試的目标 Bean,并且還要進行造型操作。

3.       資料庫現場容易遭受破壞

測試方法可能會對資料庫記錄進行更改操作,破壞資料庫現場。雖然是針對開發資料庫進行測試工作的,但如果資料操作的影響是持久的,将會形成積累效應并影響到測試用例的再次執行。舉個例子,假設在某個測試方法中往資料庫插入一條 ID 為 1 的 t_user 記錄,第一次運作不會有問題,第二次運作時,就會因為主鍵沖突而導緻測試用例執行失敗。是以測試用例應該既能夠完成測試固件業務功能正确性的檢查,又能夠容易地在測試完成後恢複現場,做到踏雪無迹、雁過無痕。

4.       不容易在同一事務下通路資料庫以檢驗業務操作的正确性

當測試固件操作資料庫時,為了檢測資料操作的正确性,需要通過一種友善途徑在測試方法相同的事務環境下通路資料庫,以檢查測試固件資料操作的執行效果。如果直接使用 JUnit 進行測試,我們很難完成這項操作。

Spring 測試架構是專門為測試基于 Spring 架構應用程式而設計的,它能夠讓測試用例非常友善地和 Spring 架構結合起來,以上所有問題都将迎刃而解。

Spring TestContext 測試架構體系結構

TestContext 核心類、支援類以及注解類

TestContext 測試架構的核心由 org.springframework.test.context 包中三個類組成,分别是 TestContext 和 TestContextManager 類以及 TestExecutionListener 接口。

每次測試都會建立

TestContextManager

TestContextManager

管理了一個

TestContext

,它負責持有目前測試的上下文。

TestContextManager

還負責在測試執行過程中更新

TestContext

的狀态并代理到

TestExecutionListener

,它用來監測測試實際的執行(如提供依賴注入、管理事務等等)

TestContext

TestContext

:封裝測試執行的上下文,與目前使用的測試架構無關。

TestContextManager

TestContextManager

:Spring TestContext Framework的主入口點,負責管理單獨的

TestContext

并在定義好的執行點上向所有注冊的

TestExecutionListener

發出事件通知:測試執行個體的準備,先于特定的測試架構的前置方法,遲于後置方法。

TestExecutionListener

TestExecutionListener

:定義了一個監聽器API與

TestContextManager

釋出的測試執行事件進行互動,而該監聽器就是注冊到這個

TestContextManager

上的。

Spring提供了

TestExecutionListener

的三個實作, 他們都是使用預設值進行配置的(通過

@TestExecutionListeners

注解):

DependencyInjectionTestExecutionListener

DirtiesContextTestExecutionListener

TransactionalTestExecutionListener

,他們對測試執行個體提供了依賴注入支援,處理

@DirtiesContext

注解,并分别使用預設的復原語義對測試提供事務支援。

DependencyInjectionTestExecutionListener:該監聽器提供了自動注入的功能,它負責解析測試用例中 @Autowried 注解并完成自動注入;

DirtiesContextTestExecutionListener:一般情況下測試方法并不會對 Spring 容器上下文造成破壞(改變 Bean 的配置資訊等),如果某個測試方法确實會破壞 Spring 容器上下文,你可以顯式地為該測試方法添加 @DirtiesContext 注解,以便 Spring TestContext 在測試該方法後重新整理 Spring 容器的上下文,而 DirtiesContextTestExecutionListener 監聽器的工作就是解析 @DirtiesContext 注解;

TransactionalTestExecutionListener:它負責解析 @Transaction、@NotTransactional 以及 @Rollback 等事務注解的注解。@Transaction 注解讓測試方法工作于事務環境中,不過在測試方法傳回前事務會被復原。你可以使用 @Rollback(false) 讓測試方法傳回前送出事務。而 @NotTransactional 注解則讓測試方法不工作于事務環境中。此外,你還可以使用類或方法級别的 @TransactionConfiguration 注解改變事務管理政策

@TestExecutionListeners

用來注冊

TestExecutionListener

@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})

@ContextConfiguration

用來指定加載的Spring配置檔案的位置,會加載預設配置檔案

1.     locations屬性

@ContextConfiguration不帶locations屬性,

GenericXmlContextLoader會基于測試類的名字産生一個預設的位置.

如果類名叫做

com.example.MyTest

,那麼

GenericXmlContextLoader

就會從

"classpath:/com/example/MyTest-context.xml"

加載應用上下文。

@ContextConfiguration 若帶有locations屬性

@ContextConfiguration(locations= { "spring-service.xml","spring-service1.xml" })  會去加載指定檔案

2.       inheritLocations屬性

@ContextConfiguration的inheritLocations屬性 (是否繼承父類的locations),預設為true

@ContextConfiguration(locations={"/base-context.xml"})

@ContextConfiguration(locations={"/extended-context.xml"})      

為true時,會将/base-context.xml, /extended-context.xml 合并加載,若有重載的bean為子類為準

為false時,會屏蔽掉父類的資源位置

@RunWith

@RunWith 注解指定測試用例的運作器

@RunWith(SpringJUnit4ClassRunner.class)

SpringJUnit4ClassRunner

Spring TestContext 架構提供了擴充于 org.junit.internal.runners.JUnit4ClassRunner 的 SpringJUnit4ClassRunner 運作器,它負責總裝 Spring TestContext 測試架構并将其統一到 JUnit 4.4 架構中。

它負責無縫地将 TestContext 測試架構移花接木到 JUnit 4.4 測試架構中,它是 Spring TestContext 可以運作起來的根本所在

持有TestContextManager的引用

privatefinal TestContextManager testContextManager;

ContextCache

緩存ApplicationContext

ContextLoader

Interface  加載context接口

GenericXmlContextLoader為預設的ContextLoader

ApplicationContextAware

自動加載ApplicationContext

AbstractJUnit4SpringContextTests

對內建了Spring TestContext Framework與JUnit 4.4環境中的

ApplicationContext

測試支援的基本測試類進行了抽取。

當你繼承

AbstractJUnit4SpringContextTests

時,你就可以通路到

protected

的成員變量:

o   

applicationContext

:使用它進行顯式的bean查找或者測試整個上下文的狀态。

AbstractTransactionalJUnit4SpringContextTests

對為JDBC通路增加便捷功能的

AbstractJUnit4SpringContextTests

的事務擴充進行抽象。 需要在

ApplicationContext

中定義一個

javax.sql.DataSource

bean和一個

PlatformTransactionManager

bean。

當你繼承

AbstractTransactionalJUnit4SpringContextTests

類時,你就可以通路到下列

protected

的成員變量:

o   

applicationContext

:繼承自父類

AbstractJUnit4SpringContextTests

。 使用它執行bean的查找或者測試整個上下文的狀态

o   

simpleJdbcTemplate

:當通過查詢來确認狀态時非常有用。例如,應用代碼要建立一個對象,然後使用ORM工具将其持久化,這時你想在測試代碼執行前後對其進行查詢,以确定資料是否插入到資料庫中。 (Spring會保證該查詢運作在相同僚務内。)你需要告訴你的ORM工具‘flush’其改變以正确完成任務,例如,使用Hibernate

Session

接口的

flush()

方法。

提示

這些類僅僅為擴充提供了友善。 如果你不想将你的測試類綁定到Spring的類上 - 例如,如果你要直接擴充你想測試的類 - 隻需要通過

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration

@TestExecutionListeners

等注解來配置你自己的測試類就可以了。

常用注解

Spring架構在

org.springframework.test.annotation

包中提供了常用的Spring特定的注解集,如果你在Java5或以上版本開發,可以在測試中使用它。

@IfProfileValue

提示一下,注解測試隻針對特定的測試環境。如果配置的

ProfileValueSource

類傳回對應的提供者的

名稱值

,這個測試就可以啟動。這個注解可以應用到一個類或者單獨的方法。

@IfProfileValue(name="java.vendor", value="Sun Microsystems Inc.")

publicvoid testJoin() {

    System.out.println(System.getProperty("java.vendor"));

}

@ProfileValueSourceConfiguration

類級别注解用來指定當通過

@IfProfileValue

注解擷取已配置的profile值時使用何種

ProfileValueSource

。如果

@ProfileValueSourceConfiguration

沒有在測試中聲明,将預設使用

SystemProfileValueSource

@ProfileValueSourceConfiguration(CustomProfileValueSource.class)      

SystemProfileValueSource中的代碼

public String get(String key) {

    Assert.hasText(key, "'key' must not be empty");

    return System.getProperty(key);

}

@DirtiesContext

在測試方法上出現這個注解時,表明底層Spring容器在該方法的執行中被“污染”,進而必須在方法執行結束後重新建立(無論該測試是否通過)。

@DirtiesContext

    publicvoid testJoinC() {       

            Assert.assertEquals(true, false);

}

@ExpectedException

表明被注解方法預期在執行中抛出一個異常。預期異常的類型在注解中給定。如果該異常的執行個體在測試方法執行中被抛出,則測試通過。同樣的如果該異常執行個體沒有在測試方法執行時抛出,則測試失敗。

@ExpectedException(MemberServiceException.class)

publicvoid testChangePassword() {}

協同使用Spring的

@ExpectedException

注解與JUnit 4的

@Test(expected=...)

會導緻一個不可避免的沖突。是以當與JUnit 4內建時,開發者必須選擇其中一個,在這種情況下建議使用顯式的JUnit 4配置。

@Timed

表明被注解的測試方法必須在規定的時間區間内執行完成(以毫秒記)。如果測試執行時間超過了規定的時間區間,測試就失敗了。

注意該時間區間包括測試方法本身的執行,任何重複測試(參見

@Repeat

),還有任何測試fixture的set up或tear down時間。

@Timed(millis=100)

    publicvoid testPersonByVerifiedMobile(){}

Spring的

@Timed

注解與JUnit 4的

@Test(timeout=...)

支援具有不同的語義。 特别地,鑒于JUnit 4處理測試執行逾時(如通過在一個單獨的

線程

中執行測試方法)的方式,我們不可能在一個事務上下文中的測試方法上使用JUnit的

@Test(timeout=...)

配置。是以,如果你想将一個測試方法配置成計時且具事務性的, 你就必須聯合使用Spring的

@Timed

@Transactional

注解。 還值得注意的是

@Test(timeout=...)

隻管測試方法本身執行的次數,如果超出的話立刻就會失敗;然而,

@Timed

關注的是測試執行的總時間(包括建立和銷毀操作以及重複),并且不會令測試失敗。

@Repeat

表明被注解的測試方法必須重複執行。執行的次數在注解中聲明。

注意重複執行範圍包括包括測試方法本身的執行,以及任何測試fixture的set up或tear down。

@Test

    @Timed(millis=100)

    @Repeat(10)

publicvoid testPersonByVerifiedMobile()

@Rollback

表明被注解方法的事務在完成後是否需要被復原。 如果

true

,事務将被復原,否則事務将被送出。 使用

@Rollback

接口來在類級别覆寫配置的預設復原标志。

@Rollback(false)

publicvoid testPersonByVerifiedMobile()

@Transactional

出現該注解表明測試方法必須在事務中執行。

@NotTransactional

出現該注解表明測試方法必須不在事務中執行。

@NotTransactional

publicvoid testPersonByVerifiedMobile()

@Autowired

按類型注入(可以寫在屬性上也可以寫在setter上)

@Autowired

Join join11;

@Autowired

    publicvoid setJoin11(Join join11) {

        this.join11 = join11;

    }

@Resource

JSR 250中注解. 與@

Autowired

類似

@Resource

    publicvoid setJoin(Join join) {

        this.join = join;

}

@Resource

    Join join;

@Autowired 與@Resource 都可以用來裝配bean.  都可以寫在字段上,或寫在set方法上

@Autowired (srping提供的) 預設按類型裝配

@Resource ( j2ee提供的 ) 預設按名稱裝配,當找不到(不寫name屬性)名稱比對的bean再按類型裝配.

可以通過@Resource(name="beanName") 指定被注入的bean的名稱, 要是指定了name屬性, 就用 字段名 去做name屬性值,一般不用寫name屬性.

@Resource(name="beanName")指定了name屬性,按名稱注入但沒找到bean, 就不會再按類型裝配了.

推薦@Autowired 與@Resource作用有字段上,  就不用寫set方法了,也不要寫@Resource(name="beanName").

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})

public@interfaceAutowired

@Qualifier

在需要更多控制的時候,任何autowired的域、構造參數、或者方法參數可以進一步加注@Qualifier注解。qualifier可以包含一個字元串值,在這種情況下,Spring會試圖通過名字來找到對應的對象。

@Autowired

    @Qualifier("join")

Join join111;

@Qualifier作為一個獨立注解存在的主要原因是它可以被應用在構造器參數或方法參數上,但上文提到的@Autowired注解隻能運用在構造器或方法本身。

public SpringTest(@Qualifier("join")Join join){

    }

看下@Qualifier的定義

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public@interfaceQualifier

@TransactionConfiguration

為配置事務性測試定義了類級别的中繼資料。特别地,如果需要的PlatformTransactionManager不是“transactionManager”的話,那麼可以顯式配置驅動事務的

PlatformTransactionManager

的bean名字。此外, 可以将

defaultRollback

标志改為

false

。通常,

@TransactionConfiguration

@ContextConfiguration

搭配使用。

@TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)

publicclass SpringTest

@BeforeTransaction

表明被注解的

public void

方法應該在測試方法的事務開始之前執行,該事務是通過

@Transactional

注解來配置的。

@AfterTransaction

表明被注解的

public void

方法應該在測試方法的事務結束之後執行,該事務是通過

@Transactional

注解來配置的。

标注 @Before 或 @After 注解的方法和測試方法運作在同一個事務中,但有時我們希望在測試方法的事務開始之前或完成之後執行某些方法以便擷取資料庫現場的一些情況。這時,可以使用 Spring TestContext 的 @BeforeTransaction 和 @AfterTransaction 注解來達到目錄(這兩個注解位于 org.springframework.test.context.transaction 包中)。

總結

Spring 的 TestContext 測試架構不但可以整合到 JUnit 4.4 測試架構上,而且還可以整合到 JUnit 3.8 以及 TestNG 等測試架構上。目前已經提供了對 JUnit 3.8 以及 TestNG 的支援,你可以分别在 org.springframework.test.context.junit38 和 org.springframework.test.context.testng 包下找到整合的幫助類。

參考資料:

1.       http://www.ibm.com/developerworks/cn/java/j-lo-spring25-test/?S_TACT=105AGX52&S_CMP=techcsdn

2.       Spring-Reference_zh_CN.chm

繼續閱讀