qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請訪問 http://qaseven.github.io/

          持續(xù)集成之路——數(shù)據(jù)訪問層單元測試遇到的問題

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

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

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

            Spring通過<jdbc:embedded-database>標簽提供對內(nèi)存數(shù)據(jù)的支持,形如:

          <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也看了很多遍,始終沒有發(fā)現(xiàn)問題。最后無意間看到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。更改后,啟動正常。所有,如果有同學遇到類似的問題,應該先檢查頭部。

            二、外鍵關(guān)聯(lián)導致的刪除失敗。

            在剛開始寫測試時,每個用例單獨運行都沒有問題,可是一旦一起運行,就出現(xiàn)下面的異常:
            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)
            ……

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

          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中構(gòu)建時,總是可以看到如下的警告信息:

            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可能會引起問題,建議設(shè)置該屬性值。解決方法也很明顯:就是設(shè)置數(shù)據(jù)庫連接的PROPERTY_DATATYPE_FACTORY屬性的值。嘗試了用Before、BeforeClass或者自定義ExecutionListener中都無法實現(xiàn)對該屬性的設(shè)置。

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

            直接上源碼吧:

            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);
              }


           綠色就是真正發(fā)揮作用的代碼。

            可是這個類并不能直接飲用,而是通過TransactionDbUnitTestExecutionListener的CHAIN被調(diào)用的,而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確保數(shù)據(jù)的清潔。使用簡單,只需要將上面的注解增加一個@Transactional,

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

            可是運行時,卻出現(xiàn)了異常:

          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


           最后通過查看源代碼發(fā)現(xiàn),CustomDbUnitTestExecutionListener會先于TransactionalTestExecutionListener執(zhí)行,而前者在執(zhí)行完畢就關(guān)閉了數(shù)據(jù)庫連接,后者在回滾時,就發(fā)生了連接不存在的異常。

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

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

            也就是數(shù)組兩個元素調(diào)換下位置。








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

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

          導航

          統(tǒng)計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 察隅县| 乌兰察布市| 南投市| 桃江县| 临漳县| 华亭县| 三门县| 陇南市| 佛教| 尉氏县| 紫阳县| 康定县| 隆尧县| 灵武市| 河西区| 汉中市| 瑞丽市| 扶绥县| 威信县| 邵武市| 宝坻区| 邻水| 秭归县| 镇沅| 策勒县| 仙居县| 银川市| 蚌埠市| 潮安县| 额尔古纳市| 融水| 炎陵县| 称多县| 怀远县| 通城县| 大渡口区| 尼玛县| 香格里拉县| 浙江省| 合川市| 台山市|