dbunit使用_摆脱困境:在DbUnit数据集中使用空值
dbunit使用
如果我們正在為使用Spring Framework的應用程序編寫集成測試,則可以通過使用Spring Test DbUnit將DbUnit與Spring測試框架集成。
但是, 這種集成并非沒有問題 。
通常,我們必須在運行測試之前向數據庫中插入空值,或者驗證保存到特定表列中的值是否為空 。 這些是非常基本的用例,但是編寫支持它們的集成測試非常棘手。
這篇博客文章指出了與空值有關的問題,并描述了如何解決它們。 讓我們從快速查看被測系統開始。
如果您不知道如何為存儲庫編寫集成測試,則應閱讀我的博客文章,標題為: Spring Data JPA教程:集成測試 。
它說明了如何為Spring Data JPA存儲庫編寫集成測試,但是您可以使用相同的方法為其他使用關系數據庫的Spring支持的存儲庫編寫測試。
被測系統
經過測試的“應用程序”具有一個實體和一個Spring Data JPA存儲庫,該存儲庫為該實體提供CRUD操作。
我們的實體類稱為Todo ,其源代碼的相關部分如下所示:
import javax.persistence.*;@Entity @Table(name="todos") public class Todo {private static final int MAX_LENGTH_DESCRIPTION = 500;private static final int MAX_LENGTH_TITLE = 100;@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@Column(name = "description", nullable = true, length = MAX_LENGTH_DESCRIPTION)private String description;@Column(name = "title", nullable = false, length = MAX_LENGTH_TITLE)private String title;@Versionprivate long version;//Constructors, builder class, and getters are omitted. }- 您可以從Github獲取Todo類的完整源代碼 。
另外,我們不應該使用構建器模式,因為在創建新的Todo對象時,我們的實體只有兩個String字段被設置。 但是,我在這里使用它是因為它使我們的測試更易于閱讀。
我們的Spring Data JPA存儲庫接口稱為TodoRepository ,它擴展了CrudRepository <T,ID擴展了Serializable>接口。 該存儲庫為Todo對象提供CRUD操作。 它還聲明了一種查詢方法,該方法返回其說明與給定搜索詞匹配的所有待辦事項條目。
TodoRepository接口的源代碼如下所示:
import org.springframework.data.repository.CrudRepository;public interface TodoRepository extends CrudRepository<Todo, Long> {List<Todo> findByDescription(String description); }補充閱讀:
- CrudRepository接口的Javadoc
- Spring Data JPA教程
- Spring Data JPA –參考文檔
讓我們繼續前進,了解在編寫用于從關系數據庫讀取信息或將信息保存到其中的代碼的集成測試時,如何處理空值。
處理空值
在為數據訪問代碼編寫集成測試時 ,我們必須在每個測試用例之前將數據庫初始化為已知狀態,并確保將正確的數據寫入數據庫。
本節確定了在編寫集成測試以解決我們遇到的問題
- 使用平面XML數據集。
- 將空值寫入數據庫,或確保表列的值為null 。
我們還將學習如何解決這些問題。
將空值插入數據庫
當我們編寫從數據庫讀取信息的集成測試時,我們必須在調用測試之前將該數據庫初始化為已知狀態,有時我們必須向數據庫中插入空值。
因為我們使用平面XML數據集,所以可以通過省略相應的屬性值將空值插入表列。 這意味著,如果我們想在todos表的description列中插入空值,則可以使用以下DbUnit數據集來做到這一點:
<dataset><todos id="1" title="FooBar" version="0"/> </dataset>但是,通常我們必須在已使用的數據庫表中插入多行。 以下DbUnit數據集( todo-entries.xml )將兩行插入todos表:
<dataset><todos id="1" title="FooBar" version="0"/><todos id="2" description="description" title="title" version="0"/> </dataset>讓我們找出對TodoRepository接口的findByDescription()方法編寫集成測試并使用先前的數據集( todo-entries.xml )初始化數據庫時會發生什么情況。 我們的集成測試的源代碼如下所示:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {PersistenceContext.class}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class }) public class ITTodoRepositoryTest {private static final Long ID = 2L;private static final String DESCRIPTION = "description";private static final String TITLE = "title";private static final long VERSION = 0L;@Autowiredprivate TodoRepository repository;@Test@DatabaseSetup("todo-entries.xml")public void findByDescription_ShouldReturnOneTodoEntry() {List<Todo> todoEntries = repository.findByDescription(DESCRIPTION);assertThat(todoEntries).hasSize(1);Todo found = todoEntries.get(0);assertThat(found.getId()).isEqualTo(ID);assertThat(found.getTitle()).isEqualTo(TITLE);assertThat(found.getDescription()).isEqualTo(DESCRIPTION);assertThat(found.getVersion()).isEqualTo(VERSION);} }當運行此集成測試時,我們得到以下斷言錯誤:
java.lang.AssertionError: Expected size:<1> but was:<0> in: <[]>這意味著從數據庫中找不到正確的待辦事項條目。 發生了什么? 我們的查詢方法是如此簡單,以至于它應該起作用,特別是因為在調用測試用例之前,我們已將正確的數據插入數據庫。
好吧,實際上兩行的描述列都是空的。 DbUnit常見問題說明了發生這種情況的原因 :
DbUnit使用表的第一個標記來定義要填充的列。 如果此表的以下記錄包含額外的列,那么將不會填充這些列。
它還提供了解決此問題的方法:
從DBUnit 2.3.0開始,有一種稱為“列感知”的功能,該功能基本上將整個XML讀入緩沖區,并在出現新列時動態添加它們。
我們可以通過反轉todos元素的順序來解決此問題,但這很麻煩,因為每次創建新數據集時我們都必須記住要做的事情。 我們應該使用列檢測,因為它消除了人為錯誤的可能性。
我們可以按照以下步驟啟用列檢測:
ColumnSensingFlatXmlDataSetLoader類的源代碼如下所示:
import com.github.springtestdbunit.dataset.AbstractDataSetLoader; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.springframework.core.io.Resource; import java.io.InputStream;public class ColumnSensingFlatXMLDataSetLoader extends AbstractDataSetLoader {@Overrideprotected IDataSet createDataSet(Resource resource) throws Exception {FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();builder.setColumnSensing(true);try (InputStream inputStream = resource.getInputStream()) {return builder.build(inputStream);}} }補充閱讀:
- FlatXmlDataSet類的Javadoc
現在,我們可以通過使用@DbUnitConfiguration批注注釋測試類并將其加載器屬性的值設置為ColumnSensingFlatXmlDataSetLoader.class,來配置測試類以使用此數據加載器 。
我們的固定集成測試的源代碼如下所示:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {PersistenceContext.class}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class }) @DbUnitConfiguration(dataSetLoader = ColumnSensingFlatXMLDataSetLoader.class) public class ITTodoRepositoryTest {private static final Long ID = 2L;private static final String DESCRIPTION = "description";private static final String TITLE = "title";private static final long VERSION = 0L;@Autowiredprivate TodoRepository repository;@Test@DatabaseSetup("todo-entries.xml")public void findByDescription_ShouldReturnOneTodoEntry() {List<Todo> todoEntries = repository.findByDescription(DESCRIPTION);assertThat(todoEntries).hasSize(1);Todo found = todoEntries.get(0);assertThat(found.getId()).isEqualTo(ID);assertThat(found.getTitle()).isEqualTo(TITLE);assertThat(found.getDescription()).isEqualTo(DESCRIPTION);assertThat(found.getVersion()).isEqualTo(VERSION);} }當我們第二次運行集成測試時,它通過了。
讓我們找出如何驗證空值是否已保存到數據庫中。
驗證表列的值是否為空
在編寫將信息保存到數據庫的集成測試時,我們必須確保將正確的信息確實保存到數據庫,有時我們必須驗證表列的值為null 。
例如,如果我們寫這證實了,當我們創建一個沒有描述一個待辦事項條目正確的信息保存到數據庫中的集成測試,我們必須確保一個空值插入到待辦事項表的說明列。
我們的集成測試的源代碼如下所示:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.DbUnitConfiguration; import com.github.springtestdbunit.annotation.ExpectedDatabase; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {PersistenceContext.class}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class }) @DbUnitConfiguration(dataSetLoader = ColumnSensingFlatXMLDataSetLoader.class) public class ITTodoRepositoryTest {private static final String DESCRIPTION = "description";private static final String TITLE = "title";@Autowiredprivate TodoRepository repository;@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-without-description-expected.xml")public void save_WithoutDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(null).build();repository.save(todoEntry);} }這不是一個很好的集成測試,因為它僅測試Spring Data JPA和Hibernate是否正常工作。 我們不應該通過為框架編寫測試來浪費時間。 如果我們不信任框架,則不應使用它。
如果您想學習為數據訪問代碼編寫好的集成測試,則應該閱讀標題為: 編寫數據訪問代碼測試的教程。
用于初始化數據庫的DbUnit數據集( no-todo-entries.xml )如下所示:
<dataset><todos/> </dataset>因為我們沒有設置保存的todo條目的描述 ,所以todos表的description列應該為null 。 這意味著我們應該從數據集中省略它,以驗證是否將正確的信息保存到數據庫中。
該數據集( save-todo-entry-without-description-expected.xml )如下所示:
<dataset><todos id="1" title="title" version="0"/> </dataset>當我們運行集成測試時,它失敗,并且我們看到以下錯誤消息:
junit.framework.ComparisonFailure: column count (table=todos, expectedColCount=3, actualColCount=4) Expected :[id, title, version] Actual :[DESCRIPTION, ID, TITLE, VERSION]問題在于DbUnit期望todos表僅具有id , title和version列。 這樣做的原因是,這些列是從數據集的第一行(也是唯一的行)中找到的唯一列。
我們可以使用ReplacementDataSet解決此問題。 ReplacementDataSet是一個修飾器,它用替換對象替換從平面XML數據集文件中找到的占位符。 讓我們修改自定義的數據集加載類返回一個ReplacementDataSet對象替換“[空]”字符串與空 。
我們可以通過對自定義數據集加載器進行以下更改來做到這一點:
ColumnSensingReplacementDataSetLoader類的源代碼如下所示:
import com.github.springtestdbunit.dataset.AbstractDataSetLoader; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.ReplacementDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.springframework.core.io.Resource;import java.io.InputStream;public class ColumnSensingReplacementDataSetLoader extends AbstractDataSetLoader {@Overrideprotected IDataSet createDataSet(Resource resource) throws Exception {FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();builder.setColumnSensing(true);try (InputStream inputStream = resource.getInputStream()) {return createReplacementDataSet(builder.build(inputStream));}}private ReplacementDataSet createReplacementDataSet(FlatXmlDataSet dataSet) {ReplacementDataSet replacementDataSet = new ReplacementDataSet(dataSet);//Configure the replacement dataset to replace '[null]' strings with null.replacementDataSet.addReplacementObject("[null]", null);return replacementDataSet;} }補充閱讀:
- IDataSet接口的最常用實現
- ReplacementDataSet類的Javadoc
我們可以按照以下步驟修復集成測試:
首先 ,我們必須配置測試類以使用ColumnSensingReplacementDataSetLoader類加載DbUnit數據集。 因為我們已經使用@DbUnitConfiguration注釋了測試類,所以我們必須將其loader屬性的值更改為ColumnSensingReplacementDataSetLoader.class 。
固定測試類的源代碼如下所示:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.DbUnitConfiguration; import com.github.springtestdbunit.annotation.ExpectedDatabase; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {PersistenceContext.class}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class }) @DbUnitConfiguration(dataSetLoader = ColumnSensingReplacementDataSetLoader.class) public class ITTodoRepositoryTest {private static final String DESCRIPTION = "description";private static final String TITLE = "title";@Autowiredprivate TodoRepository repository;@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-without-description-expected.xml")public void save_WithoutDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(null).build();repository.save(todoEntry);} }其次 ,我們必須驗證null值是否保存到todos表的description列中。 為此,我們可以向數據集中唯一的todos元素添加一個description屬性,并將description屬性的值設置為'[null]'。
我們的固定數據集( save-todo-entry-without-description-expected.xml )如下所示:
<dataset><todos id="1" description="[null]" title="title" version="0"/> </dataset>當我們運行集成測試時,它會通過。
讓我們繼續并總結從這篇博客文章中學到的知識。
摘要
這篇博客文章教會了我們四件事:
- DbUnit假定數據庫表僅包含從指定表行的列的第一個標記中找到的那些列。 如果要覆蓋此行為,則必須啟用DbUnit的列感測功能。
- 如果要確保將null值保存到數據庫,則必須使用替換數據集。
- 我們學習了如何創建自定義數據集加載器,以創建替換數據集并使用列感測。
- 我們了解了如何配置用于加載DbUnit數據集的數據集加載器。
您可以從Github獲得此博客文章的示例應用程序 。
翻譯自: https://www.javacodegeeks.com/2014/11/spring-from-the-trenches-using-null-values-in-dbunit-datasets.html
dbunit使用
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的dbunit使用_摆脱困境:在DbUnit数据集中使用空值的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 快讯丨“领跑100”发布:寻找100家智
- 下一篇: 三星One UI 6测试版升级机型清单出
