持續(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 javax.sql.DataSource; public class ForeignKeyDisabling extends AbstractTestExecutionListener { } |
把這個新的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, @Override |
那么測試類的注解也要修改:
@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) 編輯 收藏 所屬分類: 測試學習專欄 、敏捷測試