摆脱困境:在每种测试方法之前重置自动增量列

当我们为将信息保存到数据库的功能编写集成测试时,我们必须验证是否将正确的信息保存到数据库。

如果我们的应用程序使用Spring Framework,则可以为此目的使用Spring Test DbUnit和DbUnit 。

但是,很难验证是否在主键列中插入了正确的值,因为主键通常是通过使用自动增量或序列自动生成的。

这篇博客文章指出了与自动生成值的列相关的问题,并帮助我们解决了这一问题。

补充阅读:

  • 在标题为“ 从沟壑中弹跳:在DbUnit数据集中使用空值 ”的博客文章中描述了经过测试的应用程序。 我建议您阅读该博客文章,因为我不会在此博客文章上重复其内容。
  • 如果您不知道如何为存储库编写集成测试,则应阅读我的博客文章,标题为: Spring Data JPA教程:集成测试 。 它说明了如何为Spring Data JPA存储库编写集成测试,但是您可以使用与为使用关系数据库的其他Spring Powered存储库编写测试的方法相同的方法。

我们无法断言未知

让我们开始为CrudRepository接口的save()方法编写两个集成测试。 这些测试描述如下:

  • 第一个测试确保在设置了保存的Todo对象的标题和描述时将正确的信息保存到数据库中。
  • 当仅设置了保存的Todo对象的标题时,第二个测试将验证是否将正确的信息保存到数据库中。

这两个测试都通过使用相同的DbUnit数据集( no-todo-entries.xml )初始化使用的数据库,该数据集如下所示:

<dataset><todos/>
</dataset>

我们的集成测试类的源代码如下所示:

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.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
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;@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 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("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-with-title-and-description-expected.xml")public void save_WithTitleAndDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(DESCRIPTION).build();repository.save(todoEntry);}@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数据集( save-todo-entry-with-title-and-description-expected.xml )用于验证已保存的Todo对象的标题和描述是否已插入todos表中,如下所示:

<dataset><todos id="1" description="description" title="title" version="0"/>
</dataset>

DbUnit数据集( save-todo-entry-without-description-expected.xml )用于验证是否仅将已保存的Todo对象的标题插入了todos表,如下所示:

<dataset><todos id="1" description="[null]" title="title" version="0"/>
</dataset>

当我们运行集成测试时,其中之一失败,并且我们看到以下错误消息:

junit.framework.ComparisonFailure: value (table=todos, row=0, col=id) 
Expected :1
Actual   :2

原因是todos表的id列是一个自动增量列,并且首先调用的集成测试“获取”了id1。在调用第二个集成测试时,值2保存到了id列,测试失败。

让我们找出如何解决这个问题。

快速修复赢?

有两个快速解决我们的问题的方法。 这些修复程序描述如下:

首先 ,我们可以使用@DirtiesContext批注注释测试类,并将其classMode属性的值设置为DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD。这将解决我们的问题,因为在加载应用程序上下文时,我们的应用程序会创建一个新的内存数据库,并且@DirtiesContext注释可确保每个测试方法都使用新的应用程序上下文。

我们的测试类的配置如下所示:

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.annotation.DirtiesContext;
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;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersistenceContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
@DbUnitConfiguration(dataSetLoader = ColumnSensingReplacementDataSetLoader.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ITTodoRepositoryTest {}

这看起来很干净,但不幸的是,它可能会破坏我们的集成测试套件的性能,因为它会在调用每个测试方法之前创建一个新的应用程序上下文。 这就是为什么我们不应该使用@DirtiesContext批注,除非它是绝对必要的

但是,如果我们的应用程序只有少量的集成测试,则@DirtiesContext批注引起的性能损失可能是可以容忍的。 我们不应该仅仅因为它会使我们的测试变慢而放弃该解决方案。 有时这是可以接受的,并且在这种情况下,使用@DirtiesContext注释是一个很好的解决方案。

补充阅读:

  • @DirtiesContext注释的Javadoc
  • @ DirtiesContext.ClassMode枚举的Javadoc

其次 ,我们可以从数据集中省略todos元素的id属性,并将@ExpectedDatabase批注的assertionMode属性的值设置为DatabaseAssertionMode.NON_STRICT 。 这将解决我们的问题,因为DatabaseAssertionMode.NON_STRICT意味着将忽略数据集文件中不存在的列和表。

该断言模式是一个有用的工具,因为它使我们有可能忽略其信息不会被测试代码更改的表。 但是, DatabaseAssertionMode.NON_STRICT不是解决此特定问题的正确工具,因为它迫使我们编写用于验证很少内容的数据集。

例如,我们不能使用以下数据集:

<dataset><todos id="1" description="description" title="title" version="0"/><todos description="description two" title="title two" version="0"/>
</dataset>

如果我们使用DatabaseAssertionMode.NON_STRICT ,则数据集的每个“行”必须指定相同的列。 换句话说,我们必须将数据集修改为如下所示:

<dataset><todos description="description" title="title" version="0"/><todos description="description two" title="title two" version="0"/>
</dataset>

没什么大不了的,因为我们可以相信Hibernate将正确的ID插入todos表的id列中。

但是,如果每个待办事项条目都可以包含0 .. *标签,那么我们将会遇到麻烦。 假设我们必须编写一个集成测试,该测试将两个新的todo条目插入数据库并创建一个DbUnit数据集,以确保

  • 标题为“ title one”的待办事项条目具有一个名为“ tag one”的标签。
  • 标题为“标题二”的待办事项条目具有名为“标签二”的标签。

我们的最大努力如下所示:

<dataset><todos description="description" title="title one" version="0"/><todos description="description two" title="title two" version="0"/><tags name="tag one" version="0"/><tags name="tag two" version="0"/>
</dataset>

我们无法创建有用的DbUnit数据集,因为我们不知道保存到数据库中的待办事项条目的ID。

我们必须找到更好的解决方案。

寻找更好的解决方案

我们已经为我们的问题找到了两种不同的解决方案,但是它们都产生了新的问题。 第三种解决方案基于以下思想:

如果我们不知道插入到自动增量列中的下一个值,则必须在调用每个测试方法之前重置自动增量列。

我们可以按照以下步骤进行操作:

  1. 创建一个用于重置指定数据库表的自动增量列的类。
  2. 修复我们的集成测试。

让我们弄脏双手。

创建可以重置自动增量列的类

我们可以通过执行以下步骤来创建该类,该类可以重置指定数据库表的自动增量列:

  1. 创建一个名为DbTestUtil最终类,并通过向其添加私有构造函数来防止其实例化。
  2. 公共静态void resetAutoIncrementColumns()方法添加到DbTestUtil类。 此方法采用两个方法参数:
    1. ApplicationContext对象包含测试的应用程序的配置。
    2. 必须重置其自动增量列的数据库表的名称。
  3. 通过执行以下步骤来实现此方法:
    1. 获取对DataSource对象的引用。
    2. 通过使用键“ test.reset.sql.template”从属性文件( application.properties )中读取SQL模板。
    3. 打开数据库连接。
    4. 创建调用的SQL语句并调用它们。

DbTestUtil类的源代码如下所示:

import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;public final class DbTestUtil {private DbTestUtil() {}public static void resetAutoIncrementColumns(ApplicationContext applicationContext,String... tableNames) throws SQLException {DataSource dataSource = applicationContext.getBean(DataSource.class);String resetSqlTemplate = getResetSqlTemplate(applicationContext);try (Connection dbConnection = dataSource.getConnection()) {//Create SQL statements that reset the auto increment columns and invoke //the created SQL statements.for (String resetSqlArgument: tableNames) {try (Statement statement = dbConnection.createStatement()) {String resetSql = String.format(resetSqlTemplate, resetSqlArgument);statement.execute(resetSql);}}}}private static String getResetSqlTemplate(ApplicationContext applicationContext) {//Read the SQL template from the properties fileEnvironment environment = applicationContext.getBean(Environment.class);return environment.getRequiredProperty("test.reset.sql.template");}
}

附加信息:

  • ApplicationContext接口的Javadoc
  • DataSource接口的Javadoc
  • 环境接口的Javadoc
  • String.format()方法的Javadoc

让我们继续前进,找出如何在集成测试中使用此类。

修复我们的集成测试

我们可以按照以下步骤修复集成测试:

  1. 将重置的SQL模板添加到示例应用程序的属性文件中。
  2. 在调用我们的测试方法之前,重置todos表的自动增量列( id )。

首先 ,我们必须将重置的SQL模板添加到示例应用程序的属性文件中。 此模板必须使用String类的format()方法支持的格式 。 因为我们的示例应用程序使用H2内存数据库,所以我们必须将以下SQL模板添加到属性文件中:

test.reset.sql.template=ALTER TABLE %s ALTER COLUMN id RESTART WITH 1

附加信息:

  • 我们的示例应用程序的应用程序上下文配置类
  • String.format()方法的Javadoc
  • 重置H2中的自动增量
  • 如何重置MySQL自动增量列
  • PostgreSQL 9.3文档:ALTER SEQUENCE

其次 ,在调用我们的测试方法之前,我们必须重置todos表的自动增量列( id )。 我们可以通过对ITTodoRepositoryTest类进行以下更改来做到这一点:

  1. 将包含我们示例应用程序配置的ApplicationContext对象注入到测试类中。
  2. 重置待办事项表的自动增量列。

固定集成测试类的源代码如下所示(突出显示了更改):

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.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
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 java.sql.SQLException;@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 Long ID = 2L;private static final String DESCRIPTION = "description";private static final String TITLE = "title";private static final long VERSION = 0L;@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate TodoRepository repository;@Beforepublic void setUp() throws SQLException {DbTestUtil.resetAutoIncrementColumns(applicationContext, "todos");}@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-with-title-and-description-expected.xml")public void save_WithTitleAndDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(DESCRIPTION).build();repository.save(todoEntry);}@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);}
}

附加信息:

  • @Autowired批注的Javadoc
  • ApplicationContext接口的Javadoc
  • @Before注释的Javadoc

当我们第二次运行集成测试时,它们通过了。

让我们继续并总结从这篇博客文章中学到的知识。

摘要

这个博客教会了我们三件事:

  • 如果我们不知道插入值自动生成的列中的值,我们将无法编写有用的集成测试。
  • 如果我们的应用程序没有很多集成测试,则使用@DirtiesContext注释可能是一个不错的选择。
  • 如果我们的应用程序有很多集成测试,则必须在调用每种测试方法之前重置自动增量列。

您可以从Github获得此博客文章的示例应用程序 。

翻译自: https://www.javacodegeeks.com/2014/11/spring-from-the-trenches-resetting-auto-increment-columns-before-each-test-method.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361541.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

仅坚持了9天:京东今日宣布暂停火车票代购业务

仅仅只坚持了9天&#xff0c;对于京东商城销售火车票的讨论一直进行着。不论是否具有销售资质&#xff0c;还是变相的收费。到今天下午为止京东商城发表声明暂停火车票代购业务。以下是京东公告全文&#xff1a;尊敬的京东网友&#xff1a;鉴于京东商城火车票代购业务测试期间出…

path.join 与 path.resolve 的区别

1. 对于以/开始的路径片段&#xff0c;path.join只是简单的将该路径片段进行拼接&#xff0c;而path.resolve将以/开始的路径片段作为根目录&#xff0c;在此之前的路径将会被丢弃&#xff0c;就像是在terminal中使用cd命令一样。 path.join(/a, /b) // a/bpath.resolve(/a, /b…

Android IPC系列(一):AIDL使用详解

概述 AIDL可以实现进程间的通信&#xff0c;由于每个进程都是运行在独立的空间&#xff0c;不同的进程想要交互需要借助一些特殊的方式&#xff0c;AIDL就是其中的一种&#xff0c;AIDL是一种模板&#xff0c;因为实际交互过程中&#xff0c;并不是AIDL起的作用&#xff0c;具体…

如何使用单例EJB,Ehcache和MBean构建和清除参考数据缓存

在本文中&#xff0c;我将介绍如何使用单例EJB和Ehcache在Java EE中构建简单的参考数据缓存。 高速缓存将在给定的时间段后重置自身&#xff0c;并且可以通过调用REST端点或MBean方法“手动”清除。 这篇文章实际上是在以前的文章的基础上建立的 。 唯一的区别是&#xff0c;我…

深入浅出React Native 1: 环境配置

该教程主要介绍如何用react native来开发iOS&#xff0c;所以首先&#xff0c;你需要有一台mac&#xff0c;当然黑苹果也是可以的~ 创建一个react native的项目只需要安装以下五个组件~~&#xff08;但....坑爹的是&#xff0c;不FQ的话安装慢成狗呀&#xff09; 1. 安装 xco…

C#排序算法大全

C#排序算法大全 土人 2004-7-21 一、冒泡排序(Bubble) using System; namespace BubbleSorter { public class BubbleSorter { public void Sort(int[] list) { int i,j,temp; bool donefalse; j1; while((j<list.Length)&&(!done)) { donetrue; for…

Spring实战(前言:Spring容器)

Spring容器&#xff0c;顾名思义是用来容纳东西的&#xff0c;装的就是Bean。Spring容器负责创建、配置、管理Bean。spring容器有两个核心接口&#xff1a;BeanFactory和ApplicationContext接口&#xff0c;后者是前者的子接口。在基于spring的Java EE程序中&#xff0c;所有的…

具有WildFly,Arquillian,Jenkins和OpenShift的Java EE 7部署管道

技术提示&#xff03;54展示了如何Arquillianate&#xff08;Arquillianize&#xff1f;&#xff09;一个现有的Java EE项目并在WildFly在已知主机和端口上运行的远程模式下运行这些测试。 技术提示&#xff03;55展示了当WildFly在OpenShift中运行时如何运行这些测试。 这两个…

《那些年啊,那些事——一个程序员的奋斗史》——88

谁也不知道武总脑袋里面会想些什么。这天早上段伏枥还在发愁如何改进这4.3机器的电源管理的时候&#xff0c;武总突然让自己到会议室开会。 段伏枥还没坐下来的时候&#xff0c;武总推过来一台机器&#xff0c;说:“你看看&#xff0c;这机器怎么样。” 段伏枥定睛一看&#xf…

css浮动(float)及清除浮动的几种实用方法

CSS浮动是现在网页布局中使用最频繁的效果之一,而浮动可以帮我们解决很多问题,那么就让我们一起来看一看如何使用浮动. 一.css浮动(float) (1)html文档流 自窗体自上而下分成一行一行&#xff0c;并在每行中按从左到右的顺序排放元素。 (2)网页中大部分对象默认是占用文档流…

一台电脑同时添加git和bitbucket两个网站的ssh key

添加第一个ssh key 就不多说了&#xff0c;不懂的可以自己查资料 ssh-keygen -t rsa -C email_1email.com 然后一路enter就好了 假设已经添加好了git的ssh key 。现在要添加bitbucket的ssh key 首先 ssh-keygen -t rsa -C email_2email.com //同一个邮箱也可以 然后指定公钥的…

属性提取器:获取ListView即时更新其元素的最佳方法

这篇文章是关于如何处理JavaFX ListViews和TableViews的&#xff0c;以及这些控件如何得知所包含元素的更改内容。 我想知道为什么在相关书籍中没有找到关于以下模式的任何信息&#xff0c;因为这是一个非常关键的机制。 那里的许多帖子建议通过调用以下命令来强制触发ChangeEv…

基于python语言下的UI自动化测试框架搭建(四)

testsuits:案例执行 创建baidu_search1.py文件&#xff0c;这里会展示两种执行方式&#xff0c;一种是直接调用base_page中封装好的常用操作方法&#xff0c;另外一种是先调用baidu_homepage.py&#xff0c;通过baidu_homepage.py进行处理&#xff0c;再执行对应的案例&#xf…

MVC详解

模型&#xff0d;视图&#xff0d;控制器&#xff08;Modal View Controler&#xff0c;MVC&#xff09;是Xerox PARC在八十年代为编程语言Smalltalk&#xff0d;80发明的一种软件设计模式&#xff0c;至今已被广泛使用。最近几年被推荐为Sun公司J2EE平台的设计模式&#xff0c…

如何为每个URL连接设置自定义SSLSocketFactory的TrustManager

从javadoc中我们可以看到javax.net.ssl.HttpsURLConnection提供了一个静态方法来用setDefaultSSLSocketFory&#xff08;&#xff09;方法覆盖。 这样&#xff0c;您就可以提供一个自定义javax.net.ssl.TrustManager&#xff0c;它可以验证您自己的CA证书握手和验证等。但是&am…

ES6之命令妙用

很多人都听说过ES6&#xff08;也就是ECMAScript的新一代标准&#xff09;并且对她充满了向往&#xff0c;下面通过一个ES6中小知识点——let命令&#xff0c;来解开她的神秘面纱&#xff0c;让大家初步认识一下ES6的语法规范。let命令属于ES6中的一个基本语法&#xff0c;与原…

VUE-搜索过滤器

先看看效果 首先引入 <script src"https://cdn.jsdelivr.net/npm/vue"></script> HTML部分 <div id"app"><input v-modelsearch /><ul v-if"searchData.length > 0"><li v-for"item in searchData&quo…

使用spring-session外部化Spring-boot应用程序的会话状态

Spring-session是一个非常酷的新项目&#xff0c;旨在提供一种更简单的方法来管理基于Java的Web应用程序中的会话。 我最近在spring-session中探索的功能之一是它支持外部化会话状态的方式&#xff0c;而无需费心诸如Tomcat或Jetty之类的特定Web容器的内部。 为了测试spring-s…

windows2008 sp2 x64安装 ocs 2007 r2 笔记

在写这篇日志之前&#xff0c;反反复复安装了N次&#xff0c;重装操作系统好几次&#xff0c;为了让大家不再犯同样的错误&#xff0c;特留下几点要注意的&#xff1a; 1. 不要用windows2008 r2 装 ocs 2007 r2&#xff0c; 我很天真的下载了windows2008 r2 并开始装 ocs r2&am…

JS(JQEERY) 获取JSON对象中的KEY VALUE

var json { "Type": "Coding", "Height":100 };for (var key in json){alert(key); alert(json[key]);}$.each(json, function(i) {alert(json[i]);alert(i); }); 转载于:https://www.cnblogs.com/ytjjyy/archive/2012/04/17/2453340.html