摆脱困境:在DbUnit数据集中使用空值
如果我們正在為使用Spring Framework的應(yīng)用程序編寫集成測試,則可以通過使用Spring Test DbUnit將DbUnit與Spring測試框架集成。
但是, 這種集成并非沒有問題 。
通常,我們必須在運(yùn)行測試之前向數(shù)據(jù)庫中插入空值,或者驗(yàn)證保存到特定表列中的值是否為空 。 這些是非常基本的用例,但是編寫支持它們的集成測試非常棘手。
這篇博客文章指出了與null值有關(guān)的問題,并描述了如何解決它們。 讓我們從快速查看被測系統(tǒng)開始。
如果您不知道如何為存儲(chǔ)庫編寫集成測試,則應(yīng)閱讀我的博客文章,標(biāo)題為: Spring Data JPA教程:集成測試 。
它解釋了如何為Spring Data JPA存儲(chǔ)庫編寫集成測試,但是可以使用相同的方法為其他使用關(guān)系數(shù)據(jù)庫的Spring支持的存儲(chǔ)庫編寫測試。
被測系統(tǒng)
經(jīng)過測試的“應(yīng)用程序”具有一個(gè)實(shí)體和一個(gè)Spring Data JPA存儲(chǔ)庫,該存儲(chǔ)庫為該實(shí)體提供CRUD操作。
我們的實(shí)體類稱為Todo ,其源代碼的相關(guān)部分如下所示:
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類的完整源代碼 。
另外,我們不應(yīng)該使用構(gòu)建器模式,因?yàn)樵趧?chuàng)建新的Todo對(duì)象時(shí),我們的實(shí)體只有兩個(gè)String字段被設(shè)置。 但是,我在這里使用它是因?yàn)樗刮覀兊臏y試更易于閱讀。
我們的Spring Data JPA存儲(chǔ)庫接口稱為TodoRepository ,它擴(kuò)展了CrudRepository <T,ID擴(kuò)展了Serializable>接口。 該存儲(chǔ)庫為Todo對(duì)象提供CRUD操作。 它還聲明一種查詢方法,該方法返回其說明與給定搜索詞匹配的所有待辦事項(xiàng)條目。
TodoRepository接口的源代碼如下所示:
import org.springframework.data.repository.CrudRepository;public interface TodoRepository extends CrudRepository<Todo, Long> {List<Todo> findByDescription(String description); }補(bǔ)充閱讀:
- CrudRepository接口的Javadoc
- Spring Data JPA教程
- Spring Data JPA –參考文檔
讓我們繼續(xù)前進(jìn),了解在編寫用于從關(guān)系數(shù)據(jù)庫讀取信息或?qū)⑿畔⒈4娴狡渲械拇a的集成測試時(shí),如何處理空值。
處理空值
在為數(shù)據(jù)訪問代碼編寫集成測試時(shí) ,我們必須在每個(gè)測試用例之前將數(shù)據(jù)庫初始化為已知狀態(tài),并確保將正確的數(shù)據(jù)寫入數(shù)據(jù)庫。
本節(jié)確定了在編寫集成測試以解決我們遇到的問題
- 使用平面XML數(shù)據(jù)集。
- 將空值寫入數(shù)據(jù)庫或確保表列的值為null 。
我們還將學(xué)習(xí)如何解決這些問題。
將空值插入數(shù)據(jù)庫
當(dāng)我們編寫從數(shù)據(jù)庫讀取信息的集成測試時(shí),必須在調(diào)用測試之前將該數(shù)據(jù)庫初始化為已知狀態(tài),有時(shí)我們必須向數(shù)據(jù)庫中插入空值。
因?yàn)槲覀兪褂闷矫鎄ML數(shù)據(jù)集,所以可以通過省略相應(yīng)的屬性值將空值插入到表列中。 這意味著,如果我們想在todos表的description列中插入null值,則可以通過使用以下DbUnit數(shù)據(jù)集來做到這一點(diǎn):
<dataset><todos id="1" title="FooBar" version="0"/> </dataset>但是,通常我們必須在已使用的數(shù)據(jù)庫表中插入多行。 以下DbUnit數(shù)據(jù)集( todo-entries.xml )將兩行插入todos表:
<dataset><todos id="1" title="FooBar" version="0"/><todos id="2" description="description" title="title" version="0"/> </dataset>讓我們找出對(duì)TodoRepository接口的findByDescription()方法進(jìn)行集成測試并使用先前的數(shù)據(jù)集( todo-entries.xml )初始化數(shù)據(jù)庫時(shí)發(fā)生的情況。 我們的集成測試的源代碼如下所示:
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);} }當(dāng)運(yùn)行此集成測試時(shí),會(huì)出現(xiàn)以下斷言錯(cuò)誤:
java.lang.AssertionError: Expected size:<1> but was:<0> in: <[]>這意味著從數(shù)據(jù)庫中找不到正確的待辦事項(xiàng)條目。 發(fā)生了什么? 我們的查詢方法是如此簡單,以至于它應(yīng)該起作用,特別是因?yàn)樵谡{(diào)用測試用例之前,我們已將正確的數(shù)據(jù)插入數(shù)據(jù)庫。
好吧,實(shí)際上兩行的描述列都是空的。 DbUnit常見問題說明了發(fā)生這種情況的原因 :
DbUnit使用表的第一個(gè)標(biāo)記來定義要填充的列。 如果此表的以下記錄包含額外的列,那么將不會(huì)填充這些列。
它還提供了解決此問題的方法:
從DBUnit 2.3.0開始,有一種稱為“列檢測”的功能,該功能基本上將整個(gè)XML讀入緩沖區(qū),并在出現(xiàn)新列時(shí)動(dòng)態(tài)添加它們。
我們可以通過反轉(zhuǎn)todos元素的順序來解決此問題,但這很麻煩,因?yàn)槊看蝿?chuàng)建新數(shù)據(jù)集時(shí)我們都必須記住要做的事情。 我們應(yīng)該使用列檢測,因?yàn)樗巳藶殄e(cuò)誤的可能性。
我們可以按照以下步驟啟用列檢測:
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);}} }補(bǔ)充閱讀:
- FlatXmlDataSet類的Javadoc
現(xiàn)在,我們可以通過使用@DbUnitConfiguration批注注釋測試類并將其加載器屬性的值設(shè)置為ColumnSensingFlatXmlDataSetLoader.class,來配置測試類以使用此數(shù)據(jù)加載器 。
我們的固定集成測試的源代碼如下所示:
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);} }當(dāng)我們第二次運(yùn)行集成測試時(shí),它通過了。
讓我們找出如何驗(yàn)證空值是否已保存到數(shù)據(jù)庫中。
驗(yàn)證表列的值是否為空
在編寫將信息保存到數(shù)據(jù)庫的集成測試時(shí),我們必須確保將正確的信息確實(shí)保存到數(shù)據(jù)庫中,有時(shí)我們必須驗(yàn)證表列的值為null 。
例如,如果我們寫這證實(shí)了,當(dāng)我們創(chuàng)建一個(gè)沒有描述一個(gè)待辦事項(xiàng)條目正確的信息保存到數(shù)據(jù)庫中的集成測試,我們必須確保一個(gè)空值插入到待辦事項(xiàng)表的說明列。
我們的集成測試的源代碼如下所示:
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);} }這不是一個(gè)很好的集成測試,因?yàn)樗鼉H測試Spring Data JPA和Hibernate是否正常工作。 我們不應(yīng)該通過為框架編寫測試來浪費(fèi)時(shí)間。 如果我們不信任框架,則不應(yīng)使用它。
如果您想學(xué)習(xí)為數(shù)據(jù)訪問代碼編寫好的集成測試,則應(yīng)該閱讀我的教程: 編寫數(shù)據(jù)訪問代碼的測試 。
用于初始化數(shù)據(jù)庫的DbUnit數(shù)據(jù)集( no-todo-entries.xml )如下所示:
<dataset><todos/> </dataset>因?yàn)槲覀儧]有設(shè)置保存的todo條目的描述 ,所以todos表的description列應(yīng)該為null 。 這意味著我們應(yīng)該從數(shù)據(jù)集中省略它,以驗(yàn)證是否將正確的信息保存到數(shù)據(jù)庫中。
該數(shù)據(jù)集( save-todo-entry-without-description-expected.xml )如下所示:
<dataset><todos id="1" title="title" version="0"/> </dataset>當(dāng)我們運(yùn)行集成測試時(shí),它失敗,并且我們看到以下錯(cuò)誤消息:
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列。 這樣做的原因是,這些列是從數(shù)據(jù)集的第一行(也是唯一的行)中找到的唯一列。
我們可以使用ReplacementDataSet解決此問題。 ReplacementDataSet是一個(gè)裝飾器,它用替換對(duì)象替換從平面XML數(shù)據(jù)集文件中找到的占位符。 讓我們修改自定義的數(shù)據(jù)集加載類返回一個(gè)ReplacementDataSet對(duì)象替換“[空]”字符串與空 。
我們可以通過對(duì)自定義數(shù)據(jù)集加載器進(jìn)行以下更改來做到這一點(diǎn):
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;} }補(bǔ)充閱讀:
- IDataSet接口的最常用實(shí)現(xiàn)
- ReplacementDataSet類的Javadoc
我們可以按照以下步驟修復(fù)集成測試:
首先 ,我們必須配置測試類以使用ColumnSensingReplacementDataSetLoader類加載DbUnit數(shù)據(jù)集。 因?yàn)槲覀円呀?jīng)使用@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);} }其次 ,我們必須驗(yàn)證是否將空值保存到todos表的description列中。 為此,我們可以向數(shù)據(jù)集中唯一的todos元素添加一個(gè)description屬性,并將description屬性的值設(shè)置為'[null]'。
我們的固定數(shù)據(jù)集( save-todo-entry-without-description-expected.xml )如下所示:
<dataset><todos id="1" description="[null]" title="title" version="0"/> </dataset>當(dāng)我們運(yùn)行集成測試時(shí),它會(huì)通過。
讓我們繼續(xù)并總結(jié)從這篇博客文章中學(xué)到的知識(shí)。
摘要
這篇博客文章教會(huì)了我們四件事:
- DbUnit假定數(shù)據(jù)庫表僅包含從指定表行的列的第一個(gè)標(biāo)記中找到的那些列。 如果要覆蓋此行為,則必須啟用DbUnit的列感測功能。
- 如果要確保將null值保存到數(shù)據(jù)庫,則必須使用替換數(shù)據(jù)集。
- 我們了解了如何創(chuàng)建自定義數(shù)據(jù)集加載器,該加載器創(chuàng)建替換數(shù)據(jù)集并使用列感測。
- 我們了解了如何配置用于加載DbUnit數(shù)據(jù)集的數(shù)據(jù)集加載器。
您可以從Github獲得此博客文章的示例應(yīng)用程序 。
翻譯自: https://www.javacodegeeks.com/2014/11/spring-from-the-trenches-using-null-values-in-dbunit-datasets.html
總結(jié)
以上是生活随笔為你收集整理的摆脱困境:在DbUnit数据集中使用空值的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑新主板无网络驱动器(电脑新主板无网络
- 下一篇: 联通光猫如何连接无线路由器联通光猫如何桥