qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          持續集成之路——數據訪問層單元測試遇到的問題

           在編寫數據訪問層的單元測試時,遇到不少問題,有些問題可以很容易Google到解決方法,而有些只能自己研究解決。這里分享幾個典型的問題以及解決方法。

            先交代一下用到的測試框架 Spring Test + SpringTestDbUnit + DbUnit。

            一、先說一個低級的問題。

            Spring通過<jdbc:embedded-database>標簽提供對內存數據的支持,形如:

          <jdbc:embeded-database id="dataSource" type="HSQL">

            可是在啟動時,卻總是提示錯誤:

            Caused by: org.xml.sax.SAXParseException; lineNumber: 31; columnNumber: 57; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但無法找到元素 'jdbc:embedded-database' 的聲明。
            at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198)
            at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
            at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437)
            ……

            翻來覆去對標簽修改了很多次,文檔和dtd也看了很多遍,始終沒有發現問題。最后無意間看到context文件頭部對標簽的聲明上好像有問題:

          <beans xmlns=http://www.springframework.org/schema/beans
                 xmlns:p=http://www.springframework.org/schema/p
                 xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
                 xmlns:context=http://www.springframework.org/schema/context
                 xmlns:tx=http://www.springframework.org/schema/tx xmlns:jpa=http://www.springframework.org/schema/data/jpa
                 xmlns:task=http://www.springframework.org/schema/task xmlns:aop=http://www.springframework.org/schema/aop
                 xmlns:jdbc=http://www.springframework.org/schema/jdbc
                 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
                    http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.2.xsdBR>          http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
                    http://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task-3.2.xsd
                    http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/<SPAN style="COLOR: #ff0000">tx</SPAN>/spring-jdbc-3.2.xsd">

            仔細看了下,原來當時從tx處復制聲明時,只是將最后的tx改成了jdbc,卻忘記了將路徑中tx改為jdbc。更改后,啟動正常。所有,如果有同學遇到類似的問題,應該先檢查頭部。

            二、外鍵關聯導致的刪除失敗。

            在剛開始寫測試時,每個用例單獨運行都沒有問題,可是一旦一起運行,就出現下面的異常:
            Tests run: 5, Failures: 0, Errors: 3, Skipped: 0, Time elapsed: 0.879 sec <<< FAILURE! - in com.noyaxe.nso.service.DeviceServiceTest
            testInitializedForBindedSpaceForceBind(com.noyaxe.nso.service.DeviceServiceTest)  Time elapsed: 0.309 sec  <<< ERROR!
            java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no action; FK_L6IDVK78B2TLU8NO6EDJ0G6U8 table: CUSTOM_TABLE_COLUMN_SPACE_TYPE
            at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
            at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
            at org.hsqldb.jdbc.JDBCStatement.fetchResult(Unknown Source)
            ……
            ……

            Caused by: org.hsqldb.HsqlException: integrity constraint violation: foreign key no action; FK_L6IDVK78B2TLU8NO6EDJ0G6U8 table: CUSTOM_TABLE_COLUMN_SPACE_TYPE

            at org.hsqldb.error.Error.error(Unknown Source)
            at org.hsqldb.StatementDML.performReferentialActions(Unknown Source)
            at org.hsqldb.StatementDML.delete(Unknown Source)
            at org.hsqldb.StatementDML.executeDeleteStatement(Unknown Source)
            at org.hsqldb.StatementDML.getResult(Unknown Source)
            at org.hsqldb.StatementDMQL.execute(Unknown Source)
            at org.hsqldb.Session.executeCompiledStatement(Unknown Source)
            at org.hsqldb.Session.executeDirectStatement(Unknown Source)
            at org.hsqldb.Session.execute(Unknown Source)
            at org.hsqldb.jdbc.JDBCStatement.fetchResult(Unknown Source)
            ……

           看異常信息,應該是刪除記錄時,外鍵級聯導致的問題。在實體類里改變級聯設置并不起作用。最后在StackOverflow上找了一個解決方法:編寫一個類,繼承AbstractTestExecutionListener,在beforeTestClass中取消級聯依賴。具體如下:

          import org.dbunit.database.DatabaseDataSourceConnection;
          import org.dbunit.database.IDatabaseConnection;
          import org.springframework.test.context.TestContext;
          import org.springframework.test.context.support.AbstractTestExecutionListener;

          import javax.sql.DataSource;

          public class ForeignKeyDisabling extends AbstractTestExecutionListener {
              @Override
              public void beforeTestClass(TestContext testContext) throws Exception {
                  IDatabaseConnection dbConn = new DatabaseDataSourceConnection(
                          testContext.getApplicationContext().getBean(DataSource.class)
                  );
                  dbConn.getConnection().prepareStatement("SET DATABASE REFERENTIAL INTEGRITY FALSE").execute();

              }
          }

            把這個新的Listener添加測試類的注解中:

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration("classpath:applicationContext-test.xml")
          @TestExecutionListeners({
                  DependencyInjectionTestExecutionListener.class,
                  DirtiesContextTestExecutionListener.class,
                  TransactionDbUnitTestExecutionListener.class,
                  ForeignKeyDisabling.class})

            參考:http://stackoverflow.com/questions/2685274/tdd-with-hsqldb-removing-foreign-keys

            三、PROPERTY_DATATYPE_FACTORY引起的警告

            在jenkins中構建時,總是可以看到如下的警告信息:

            WARN   getDataTypeFactory, Potential problem found: The configured data type factory 'class org.dbunit.dataset.datatype.DefaultDataTypeFactory' might cause problems with the current database 'HSQL Database Engine' (e.g. some datatypes may not be supported properly). In rare cases you might see this message because the list of supported database products is incomplete (list=[derby]). If so please request a java-class update via the forums.If you are using your own IDataTypeFactory extending DefaultDataTypeFactory, ensure that you override getValidDbProducts() to specify the supported database products.

            意思很好理解,就說默認的DataTypeFactory可能會引起問題,建議設置該屬性值。解決方法也很明顯:就是設置數據庫連接的PROPERTY_DATATYPE_FACTORY屬性的值。嘗試了用Before、BeforeClass或者自定義ExecutionListener中都無法實現對該屬性的設置。

            那就只能先找到拋出這個異常的位置,然后向前推,逐步找到獲取連接的地方。最后發現,連接是在DbUnitTestExecutionListener.prepareDatabaseConnection中獲取連接,并且沒有做什么進一步的處理,所以前面的設置都不起作用。看來又只能通過重寫源代碼來達成目的了。

            直接上源碼吧:

            CustomTransactionDbUnitTestExecutionListener類: 完全復制DbUnitTestExecutionListener,只是增加一句代碼。注意該類的包路徑和DbUnitTestExecutionListener一致。

          private void prepareDatabaseConnection(TestContext testContext, String databaseConnectionBeanName) throws Exception {
                  Object databaseConnection = testContext.getApplicationContext().getBean(databaseConnectionBeanName);
                  if (databaseConnection instanceof DataSource) {
                      databaseConnection = DatabaseDataSourceConnectionFactoryBean.newConnection((DataSource) databaseConnection);
                  }
                  Assert.isInstanceOf(IDatabaseConnection.class, databaseConnection);
           <SPAN style="COLOR: #33cc00">((IDatabaseConnection)databaseConnection).getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new HsqldbDataTypeFactory());
          </SPAN>        testContext.setAttribute(CONNECTION_ATTRIBUTE, databaseConnection);
              }


           綠色就是真正發揮作用的代碼。

            可是這個類并不能直接飲用,而是通過TransactionDbUnitTestExecutionListener的CHAIN被調用的,而TransactionDbUnitTestExecutionListener同樣無法更改,同樣只能建一個自定義的TransactionDbUnitTestExecutionListener類,CustomTransactionDbUnitTestExecutionListener:

          public class CustomTransactionDbUnitTestExecutionListener extends TestExecutionListenerChain {

              private static final Class<?>[] CHAIN = { TransactionalTestExecutionListener.class,
                      CustomDbUnitTestExecutionListener.class };

              @Override
              protected Class<?>[] getChain() {
                  return CHAIN;
              }
          }

            那么測試類的注解也要修改:

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration("classpath:applicationContext-test.xml")
          @TestExecutionListeners({
                  DependencyInjectionTestExecutionListener.class,
                  DirtiesContextTestExecutionListener.class,
                  CustomTransactionDbUnitTestExecutionListener.class,
                  ForeignKeyDisabling.class})

            四、@Transactional標簽引起的問題

            按照spring-dbunit-test的文檔中說法,可以使用@Transactional確保數據的清潔。使用簡單,只需要將上面的注解增加一個@Transactional,

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration("classpath:applicationContext-test.xml")
          @Transactional
          @TestExecutionListeners({
                  DependencyInjectionTestExecutionListener.class,
                  DirtiesContextTestExecutionListener.class,
                  CustomTransactionDbUnitTestExecutionListener.class,
                  ForeignKeyDisabling.class})

            可是運行時,卻出現了異常:

          org.springframework.transaction.TransactionSystemException: Could not roll back JPA transaction; nested exception is javax.persistence.PersistenceException: unexpected error when rollbacking

          at org.springframework.orm.jpa.JpaTransactionManager.doRollback(JpaTransactionManager.java:544)

          at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:846)

          at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:823)

          at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:588)

          at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:297)

          at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:192)

          ……

          Caused by: javax.persistence.PersistenceException: unexpected error when rollbacking

          at org.hibernate.ejb.TransactionImpl.rollback(TransactionImpl.java:109)

          at org.springframework.orm.jpa.JpaTransactionManager.doRollback(JpaTransactionManager.java:540)

          ... 32 more

          Caused by: org.hibernate.TransactionException: rollback failed

          at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:215)

          at org.hibernate.ejb.TransactionImpl.rollback(TransactionImpl.java:106)

          ... 33 more

          Caused by: org.hibernate.TransactionException: unable to rollback against JDBC connection

          at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doRollback(JdbcTransaction.java:167)

          at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:209)

          ... 34 more

          Caused by: java.sql.SQLNonTransientConnectionException: connection exception: connection does not exist

          at org.hsqldb.jdbc.Util.sqlException(Unknown Source)

          at org.hsqldb.jdbc.Util.sqlException(Unknown Source)

          ……

          ... 35 more

          Caused by: org.hsqldb.HsqlException: connection exception: connection does not exist

          at org.hsqldb.error.Error.error(Unknown Source)

          at org.hsqldb.error.Error.error(Unknown Source)

          ... 40 more


           最后通過查看源代碼發現,CustomDbUnitTestExecutionListener會先于TransactionalTestExecutionListener執行,而前者在執行完畢就關閉了數據庫連接,后者在回滾時,就發生了連接不存在的異常。

            解決方法很簡單,修改CustomTransactionalDbUnitTestExecutionListener:

          private static final Class<?>[] CHAIN = {CustomDbUnitTestExecutionListener.class, TransactionalTestExecutionListener.class};

            也就是數組兩個元素調換下位置。








          posted on 2013-07-25 10:37 順其自然EVO 閱讀(717) 評論(0)  編輯  收藏 所屬分類: 測試學習專欄敏捷測試

          <2013年7月>
          30123456
          78910111213
          14151617181920
          21222324252627
          28293031123
          45678910

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 兰州市| 新蔡县| 志丹县| 科技| 尚志市| 千阳县| 东海县| 琼结县| 中西区| 登封市| 兴宁市| 洛浦县| 格尔木市| 邢台市| 二连浩特市| 自贡市| 清镇市| 嘉祥县| 上饶市| 柳河县| 吉木乃县| 广州市| 南通市| 永德县| 慈溪市| 渭源县| 开封市| 龙里县| 浑源县| 汉寿县| 贡嘎县| 达拉特旗| 黎川县| 萨嘎县| 广平县| 沧州市| 大洼县| 肇源县| 黄浦区| 土默特右旗| 福鼎市|