本着“不寫單元測試的程序員不是好程序員”原則,我在堅持寫着單元測試,不敢說所有的Java web應用都基於Spring,但至少一半以上都是基於Spring的。
發現通過Spring進行bean管理后,做測試會有各種不足,
例如,很多人做單元測試的時候,還要在Before方法中,初始化Spring容器,導致容器被初始化多次。
- @Before
- public void init() {
- ApplicationContext ctx = new FileSystemXmlApplicationContext( "classpath:spring/spring-basic.xml");
- baseDao = (IBaseDao) ctx.getBean("baseDao");
- assertNotNull(baseDao);
- }
在開發基於Spring的應用時,如果你還直接使用Junit進行單元測試,那你就錯過了Spring滿漢全席中最重要的一道硬菜。
再說這道菜之前,我們先來討論下,在基於Spring的javaweb項目中使用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在同一事務中訪問數據庫,查詢數據的變化,驗證操作的正確性!
看完上面的內容,相信,你已經知道我說的硬菜是什么了。
根據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在同一事務中訪問數據庫,查詢數據的變化,驗證操作的正確性!
下面,讓我們看看,使用Spring測試套件后,代碼是如何變優雅的。
1. 加入依賴包
- JUnit 4
- Spring Test (Spring框架中的test包)
- Spring 相關其他依賴包(不再贅述了,就是context等包)
如果使用maven,在基於spring的項目中添加如下依賴:
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.9</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version> 3.2.4.RELEASE </version>
- <scope>provided</scope>
- </dependency>
2. 創建測試源目錄和包
在此,推薦創建一個和src平級的源文件目錄,因為src內的類都是為日后產品准備的,而此處的類僅僅用於測試。而包的名稱可以和src中的目錄同名,這樣由於在test源目錄中,所以不會有沖突,而且名稱又一模一樣,更方便檢索。這也是Maven的約定。
3、創建測試類
1)基類,其實就是用來加載配置文件的
- @RunWith(SpringJUnit4ClassRunner.class) //使用junit4進行測試
- @ContextConfiguration
- ({"/spring/app*.xml","/spring/service/app*.xml"}) //加載配置文件
- //------------如果加入以下代碼,所有繼承該類的測試類都會遵循該配置,也可以不加,在測試類的方法上///控制事務,參見下一個實例
- //這個非常關鍵,如果不加入這個注解配置,事務控制就會完全失效!
- //@Transactional
- //這里的事務關聯到配置文件中的事務控制器(transactionManager = "transactionManager"),同時//指定自動回滾(defaultRollback = true)。這樣做操作的數據才不會污染數據庫!
- //@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
- //------------
- public class BaseJunit4Test {
- }
2)接着是我們自己的測試類
- public class UserAssignServiceTest extends BaseJunit4Test{
- @Resource //自動注入,默認按名稱
- private IBaseDao baseDao;
- @Test //標明是測試方法
- @Transactional //標明此方法需使用事務
- @Rollback(false) //標明使用完此方法后事務不回滾,true時為回滾
- public void insert( ) {
- String sql="insert into user(name,password) values(?,?)";
- Object[] objs=new Object[]{"00","000"};
- baseDao.insert( sql , objs );
- String sql1="select * from user where name=? and password=? ";
- List<Map<String,Object>> list=baseDao.queryForList( sql1 , objs );
- System.out.println(list);
- assertTrue(list.size( )>0);
- }
- }
原文:http://blog.csdn.net/shan9liang/article/details/40452469