使用TestContainers进行数据库测试

如果您曾经编写过测试数据库交互的代码,例如数据访问对象,那么您很可能遇到了测试中最长期的烦恼之一:为了准确地测试这些交互,需要一个数据库。

为了本文的方便,让我们考虑一个将PostgreSQL用作其环境的一部分的应用程序,因为这是示例所使用的。 同样,尽管H2被广泛提及,但这绝不是要贬低它的意思-在正确的位置使用它是一个很好的工具。

问题

已经提出了解决该问题的各种方法,但是似乎总是存在一些缺点。

一种测试方法是使用内存数据库,例如H2。

优点:

  • 数据库在虚拟机本地
  • 数据库生命周期由构建过程管理
  • 初始状态由构建过程或测试管理

缺点:

  • 您没有准确地对环境建模
  • 并非支持生产数据库的所有功能
  • 不同的数据类型意味着不同的列定义
  • 涉及相同表的多个测试不能并行运行而不会产生冲突

如果您认为这些约束是不可接受的,则可以考虑保留一个众所周知的正在运行的PostgreSQL数据库实例用于测试。

优点:

  • 与生产数据库100%兼容

缺点:

  • 不保证初始数据状态
  • 同一版本中涉及相同表的多个测试不能并行运行而不会产生冲突
  • 并行构建可能导致结果不一致
  • 运行本地测试的开发人员可能会破坏持续的集成构建

这种方法的进一步改进是使每个开发人员都拥有自己的PostgreSQL数据库实例。

优点:

  • 与生产数据库100%兼容
  • 开发人员构建不会干扰持续集成构建

缺点:

  • 不保证初始数据状态
  • 同一版本中涉及相同表的多个测试不能并行运行而不会产生冲突
  • 并行构建可能导致结果不一致
  • 开发人员必须保持其数据库实例为最新(或必须添加工具来对此进行管理)

使用这些方法中的每一种,我都认为不利之处足以部分或完全抵消优点。

外卖

分解最后三段,我们可以看到以下功能是理想的:

  • 数据库应绑定到测试(而不是虚拟机)
    • 这意味着现在可以进行测试并行化了
  • 数据库生命周期应由内部版本管理
  • 数据库应与生产中使用的数据库相同

我最喜欢的新解决方案

使用TestContainers ,我们可以勾选每个功能。 使用JUnit @Rule ,TestContainers将启动每个测试的Docker映像,该映像提供一个寿命与测试一样长的数据库。 由于每个Docker实例都是完全隔离的,因此可以并行运行测试以加快构建速度。

最后一点非常重要,因为如上所述,似乎总是存在一些缺点。 在这种情况下,启动Docker映像及其包含的所有内容的开销将增加您的总体构建时间。 我会(并且确实)认为增加的测试时间几乎不会影响拥有我们所有所需功能的好处。

TestContainers开箱即用支持的每个数据库都有一个特定规则,该规则可用于获取连接到数据库所需的所有详细信息。

public class FooDaoTest {@Rulepublic PostgreSQLContainer postgres = new PostgreSQLContainer();@Beforepublic void setUp() {// populate database// postgres.getDriverClassName()// postgres.getJdbcUrl()// postgres.getUsername()// postgres.getPassword()}
}

或者...

根据文档 ,可以通过将JDBC URL更改为包含tc:来启动新容器,例如jdbc:tc:postgresql://hostname/databasename 。 但是,由于驱动程序中存在这一行 ,因此在我的应用程序中失败了。

if (!url.startsWith("jdbc:postgresql:")) {

轶事

在这里,我花了10分钟时间将应用程序从使用H2切换为使用Dockerized PostgreSQL,这使我的生活变得更加简单。 我们使用jOOQ进行数据库交互,发现自己不得不删除一些非常好的jOOQ功能,因为H2不支持它们。

让我重复一遍。 由于测试环境的限制,我们面临着不断变化的生产代码

那是不可能的,而且永远不会是一个可以接受的情况,因此TestContainers的发现既是偶然的也是节省时间的。 太好了,因为它确实提供了我们所需的东西,但是省时? 我刚才说这会增加测试时间,怎么说呢? 很简单–我不需要花时间查看是否有H2模式可以支持我正在使用的功能。 我发现自己写的代码以后必须删除,因为H2不允许这样做; 我可以编写测试和与DB相关的代码,然后就完成了。

哇,您没有提到Play的整个博客文章吗?

不。 根据我刚刚提到的应用程序,这是在Play上使用它的一种简便方法。

首先,创建一个将TestContainer与Play的数据库支持结合在一起的mixin。

package be.objectify.tcexample.db;import com.google.common.collect.ImmutableMap;
import org.testcontainers.containers.PostgreSQLContainer;
import play.db.Database;
import play.db.Databases;
import play.db.evolutions.Evolutions;public interface DbTestSupport {default Database create(final PostgreSQLContainer postgres) throws Exception {final Database database = Databases.createFrom("default",postgres.getDriverClassName(),postgres.getJdbcUrl(),ImmutableMap.of("username", postgres.getUsername(),"password", postgres.getPassword()));Evolutions.applyEvolutions(database);return database;}default void destroy(final Database database) {Evolutions.cleanupEvolutions(database);database.shutdown();}
}

我在这里使用mixin的原因是因为倾向于在接口旁边定义DAO测试-请参阅我的[上一篇文章](http://www.objectify.be/wordpress/2013/06/01/a-good-lazy-way -to-write-tests /)。 如果可以将测试定义为mixins,那就更好了,因为可以将通用数据库设置代码放入一个通用类中,然后可以将其扩展以实现测试mixins,但是JUnit无法识别以这种方式定义的测试。

因此,抽象测试类不知道它具有需要数据库的实现-它仅测试接口的约定。

package be.objectify.tcexample;import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;public abstract AbstractUserDaoTest {@Testpublic void testFoo() {assertThat(dao().something()).isEqualTo(whatever);}// many, many more testspublic abstract UserDao dao();
}

通过我们特定于数据库的实现作为后盾,我们现在可以确保我们的实现按照合同要求的方式运行。

package be.objectify.tcexample.db;import be.objectify.tcexample.AbstractUserDaoTest;
import be.objectify.tcexample.UserDao;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.testcontainers.containers.PostgreSQLContainer;
import play.db.Database;public class JooqUserDaoTest extends AbstractUserDaoTest implements DbTestSupport,TestData {@Rulepublic PostgreSQLContainer postgres = new PostgreSQLContainer();private Database database;@Beforepublic void setup() throws Exception {// the database has all evolutions applieddatabase = create(postgres); // load some test dataloadTestData(database); }@Afterpublic void tearDown() {destroy(database);}@Overridepublic UserDao dao() {return new JooqUserDao(database);}
}

现在,我们的JooqUserDao实现将针对生产中使用的数据库类型的真实实例运行。

JooqUserDaoTest使用的TestData接口只是将某些数据加载到数据库中的另一个mixin。 实现并不是特别重要,因为它很大程度上取决于您自己的要求,但是它看起来可能像这样。

package be.objectify.tcexample.db;import org.jooq.impl.DSL;
import play.db.Database;import java.sql.Connection;
import java.sql.Timestamp;
import java.time.Instant;import static be.objectify.tcexample.db.jooq.generated.Tables.ACCOUNT;public interface TestData {default void loadTestData(Database database) {database.withConnection((Connection conn) -> {DSL.using(conn).insertInto(ACCOUNT,ACCOUNT.ID,ACCOUNT.KEY,ACCOUNT.CREATED_ON).values(1,"test-account-a",Timestamp.from(Instant.now())).execute();DSL.using(conn).insertInto(ACCOUNT,ACCOUNT.ID,ACCOUNT.KEY,ACCOUNT.CREATED_ON).values(2,"test-account-b",Timestamp.from(Instant.now())).execute();});}
}

翻译自: https://www.javacodegeeks.com/2017/03/database-testing-testcontainers.html

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

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

相关文章

cuda安装配置VS2013

1.1 安装cuda 首先官网下载安装包,这个就不细说了 ,我下的是这个版本cuda_8.0.61_win10.exe,,注意win7和win10版本要对应,千万别弄错了 之后双击会出现如下截图,解压完以后一直点下一步如下图出现这个等半个…

两列布局、三列适应布局、两列等高适应布局。

一. 两列布局&#xff1a;左侧定宽、右侧自适应。 四种方法 &#xff1a;flex 、position、float和负外边距、外边距 1. 使用flex. <!DOCTYPE html> <html> <head><meta charset"utf-8"><title></title><style type"tex…

操作系统的线程和进程的区别_面试官:你熟悉多线程嘛?线程跟进程有什么区别?...

这篇文章跟大家聊聊线程&#xff0c;讲到线程&#xff0c;⼜不得不提进程了~进程我们估计是很了解的了&#xff0c;在windows下打开任务管理器&#xff0c;可以发现我们在操作系统上运⾏的程序都是进程。什么是叫一个进程&#xff1f; 什么叫一个线程&#xff1f;进程&#xff…

以OpenCV为例配置VS第三方库

正常情况下&#xff0c;你拿到的别人的第三方动态库&#xff08;静态库调用方式这里不讲&#xff0c;有另外一篇文章&#xff09;至少应该包含下面三种文件&#xff1a; 1. 头文件&#xff0c;这个一般放在include这样字眼的文件夹下&#xff0c;比如OpenCV的&#xff1a;然后i…

光纤接口怎么接 图解_光纤的数据比网线快很多倍,但为什么没有在家庭局域网中普及呢?...

光纤的数据吞吐量比网线大很多&#xff0c;速度也更快&#xff0c;网络光纤入户已经普及&#xff0c;但是家庭内组网却没有普及&#xff0c;大部分家庭组网还是网线较多。分析起来主要的原因有&#xff1a;光纤线的价格和网线差不多&#xff0c;但是因为光纤所使用的专用的光接…

项目Alpha冲刺--1/10

项目Alpha冲刺--1/10作业要求 这个作业属于哪个课程软件工程1916-W(福州大学)这个作业要求在哪里项目Alpha冲刺团队名称基于云的胜利冲锋队项目名称云评&#xff1a;高校学生成绩综合评估及可视化分析平台这个作业的目标团队的代码规范、本次冲刺任务与计划其他参考文献1.邹欣.…

Java连接postgresql数据库

1.下载驱动jar 下载地址&#xff1a;https://jdbc.postgresql.org/download.html 2.导入jar包 新建lib文件夹&#xff0c;将下载的jar驱动包拖到文件夹中。 将jar驱动包添加到Libraries 3.程序代码如下&#xff1a;HelloWorld.java package test; import java.sql.Connecti…

jvm默认的初始化参数_您是否应该信任JVM中的默认设置?

jvm默认的初始化参数如今&#xff0c;JVM被认为是智能的。 预期配置不多-只需设置要在启动脚本中使用的最大堆&#xff0c;您就可以进行了。 所有其他默认设置都很好。 大概我们当中有些人误以为。 实际上&#xff0c;在运行时期间发生了很多事情&#xff0c;无法自动调整性能&…

setGeometry

setGeometry (9,9, 50, 25) 从屏幕上&#xff08;9&#xff0c;9&#xff09;位置开始&#xff08;即为最左上角的点&#xff09;&#xff0c;显示一个50*25的界面&#xff08;宽50&#xff0c;高25&#xff09; 如果在控件中加上了layout布局&#xff0c;就会发现发现没有办法…

python如何输出两行_python pandas dataframe拆分行(某些行一行拆成多行)

简单循环 最简单的方式就是循环拆分一下呗。先上最简单方法&#xff1a;import pandas as pd df pd.DataFrame({A:[1,2,3],B:[1,2,3,4,5,6],C:[3,3,3]}) result pd.DataFrame(columns[A,B,C]) print(df,\n)for i in df.itertuples(): for j in i[2].split(,): result result…

Oracle启动操作

转自&#xff1a;https://www.cnblogs.com/mellowsmile/p/4610942.html 1、启动oracle数据库&#xff1a; 从root切换到oracle用户进入&#xff1a;su - oracle 进入sqlplus环境&#xff0c;nolog参数表示不登录&#xff1a;sqlplus /nolog 以管理员模式登录&#xff1a;sqlplu…

在JPA中处理Java的LocalDateTime

几天前&#xff0c;我在处理JPA中的LocalDateTime属性时遇到问题。 在这篇博客文章中&#xff0c;我将尝试创建一个示例问题来说明该问题以及我使用的解决方案。 考虑以下实体&#xff0c;该实体为特定公司的员工建模– Entity Getter Setter public class Employee {IdGener…

Junit4 简单教程

一、环境搭建 对于习惯使用Eclipse开发平台来说&#xff0c;Junit早已是非常通常的插件&#xff0c;在Eclipse开发平台中&#xff0c;可以非常方便地搭建Junit测试环境。 1、在Eclipse上创建工程&#xff0c;任何Java工程都行。 2、引入Junit的libraries库&#xff0c;选用Juni…

Qt显示中文的方法

QT 显示中文的三种方法 最近在用Qt做项目&#xff0c;遇到UI不能显示中文的问题&#xff0c;发现有3种方法可以解决&#xff0c;先记录一下 方法一 QString::fromLocal8Bit("你好中国")1 方法二 QStringLiteral("你好中国")1 方法三 在.…

python中exception类的_面试题 | 列举几个Python中的标准异常类?

【摘要】今天给大家解答一道Python常见的面试题&#xff0c;希望这个面试栏目&#xff0c;给那些准备面试的同学&#xff0c;提供一点点帮助!小编会从最基础的面试题开始&#xff0c;每天一题。如果参考答案不够好&#xff0c;或者有错误的话&#xff0c;麻烦大家可以在留言区给…

jsonp和CORS跨域实现

一、jsonp&#xff0c;使用jquery封装的$.ajax&#xff0c;返回数据类型要设置为jsonp 示例&#xff1a; $.ajax({type: get,contentType: "application/json; charsetutf-8",url: "http://localhost:8080/aqi/getCityList.php",dataType: jsonp,< /spa…

Qt数字与字符串之间的相互转换

把QString转换为 double类型方法1.QString str"123.45";double valstr.toDouble(); //val123.45方法2.很适合科学计数法形式转换bool ok;double d;dQString("1234.56e-02").toDouble(&ok); //oktrue;d;12.3456.把QString转换为float形1.QString str&qu…

无线ap软件_无线WIFI网络干扰优化,不懂必看!新手在家里也能用的网络知识

由于国家频谱资源分配中保留一些频段用于工业、科研、医疗等方面应用&#xff0c;即ISM频段&#xff0c;如2.400到2.483G和5.170到5.825G。使用这些频段都无需许可证&#xff0c;只需要遵守一定的发射功率任何厂家都可以使用&#xff0c;WLAN也采用ISM频段。随着WLAN技术在企业…

postgresql 比较两个时间差大于 N个小时

摘要: PG 中时间想减后为interval &#xff0c;比较两个时间大于某个小时或者分钟等可以直接通过interval来实现 example1&#xff1a; -- 判断两个时间差大于4个小时 select timestamp 2013-11-11 10:00:00 - timestamp 2013-11-08 12:00:00 > interval 4 hour; examp…

spring mvc重定向_Spring的Web MVC –重定向到内存泄漏

spring mvc重定向他们说一块岩石会引起雪崩。 最近&#xff0c;我的一位同事Marcin Radoszewski给了我一块岩石。 您可能永远不会猜到它是什么&#xff0c;但是有机会在许多Web应用程序中使用它。 请允许我向您介绍这块石头。 您可能很清楚发布模式后的重定向 。 使用Spring F…