Java 与'嵌入式' PostgreSQL 数据库的单元测试

栏目: Java · 发布时间: 4年前

内容简介:在我们对数据库 DAO 类进行单元测试时,通常不应该依赖于一个外部数据库,所以会选用特定比较接近于真实数据库类型的内存或嵌入式数据库,如 HSQLDB(HyperSQL), H2, Derby 等。但总难免会用到特定数据库的特性,这时候就无法用前述各种数据库进行测试了。非要单元测试中覆盖到所用的数据库特性的话可以选择用 docker,如这里就不走 Testcontainers 那条路 -- 要求构建服务器上也要有 docker。早先希望能找到一种嵌入式或内存 PostgreSQL 数据库,后来发现 Post

在我们对数据库 DAO 类进行单元测试时,通常不应该依赖于一个外部数据库,所以会选用特定比较接近于真实数据库类型的内存或嵌入式数据库,如 HSQLDB(HyperSQL), H2, Derby 等。但总难免会用到特定数据库的特性,这时候就无法用前述各种数据库进行测试了。非要单元测试中覆盖到所用的数据库特性的话可以选择用 docker,如 Testcontainers , 经过模块扩展,它可以由 docker 来启动许多种类型的数据库,MySQL, Postgres, Oracle-XE, MS SQL Server, Couchbase 等等,详情见 Database containers 。刚了解到的是它的模块化的无限可能,像支持 Kafka Containers 和 Localstack Module 等。

这里就不走 Testcontainers 那条路 -- 要求构建服务器上也要有 docker。早先希望能找到一种嵌入式或内存 PostgreSQL 数据库,后来发现 PostgreSQL 未能提供 In-Process 和 In-Memory 的启动方式,好在 PostgreSQL 是开源,有人可以把它改造为小型的可由测试代码启停的本地数据库。有两个具有代表性的组件,分别是 OpenTable Embedded PostgreSQL ComponentEmbedded PostgreSQL Server ,它们都号称是 Embedded,所谓嵌入式,其实是进测试进程外的数据库。

下面简单体验下两个组件的用法

OpenTable Embedded PostgreSQL Component

在 Maven 项目中引入该组件

<dependency>
    <groupId>com.opentable.components</groupId>
    <artifactId>otj-pg-embedded</artifactId>
    <version>0.13.1</version> <!-- 当前版本是 0.13.1 -->
    <scope>test</scope>
</dependency>

在单元测试类中控制 PostgreSQL 起停的最基本代码

@Rule
public SingleInstancePostgresRule pg = EmbeddedPostgresRules.singleInstance();

在测试中就可以用下面的方式获得相应的数据源,不用关心启动 PostgreSQL 所用的端口号,它会随机选用端口号

Java 与'嵌入式' PostgreSQL 数据库的单元测试

然后进行执行自己的数据库初始化代码,创建新表,插入测试数据等等。

上面 @Rule 在初始和终止时有类似下面的信息日志输出(节选了重要部分)

2019-06-04 02:51:49,873 INFO class=EmbeddedPostgres thread=main event_description="Detected a Darwin x86_64 system"

2019-06-04 02:51:50,043 INFO class=EmbeddedPostgres thread=main event_description="Postgres binaries at /var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/embedded-pg/PG-3d7ce5d05cd575a649dd635576931b19 "

......................

2019-06-04 02:51:50,113 INFO class=init-aa136468-71bb-40a5-9d6b-0284db0eaa86:initdb thread=log:pid(64860) event_description="fixing permissions on existing directory /var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/epg1343892286024759082 ... ok"

......................

2019-06-04 02:51:51,859 INFO class=init-aa136468-71bb-40a5-9d6b-0284db0eaa86:initdb thread=log:pid(64860) event_description=" /var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/embedded-pg/PG-3d7ce5d05cd575a649dd635576931b19/bin/pg_ctl -D /var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/epg1343892286024759082 -l logfile start "

2019-06-04 02:51:51,857 INFO class=EmbeddedPostgres thread=main event_description="aa136468-71bb-40a5-9d6b-0284db0eaa86 initdb completed in 00:00:01.810"

2019-06-04 02:51:51,875 INFO class=EmbeddedPostgres thread=main event_description="aa136468-71bb-40a5-9d6b-0284db0eaa86 postmaster started as java.lang.UNIXProcess@481a996b on port 60180. Waiting up to PT10S for server startup to finish."

2019-06-04 02:51:51,935 INFO class=pg-aa136468-71bb-40a5-9d6b-0284db0eaa86 thread=log:pid(64871) event_description="waiting for server to start....2019-06-03 21:51:51.935 CDT [64873] LOG: listening on IPv6 address "::1", port 60180"

2019-06-04 02:51:51,936 INFO class=pg-aa136468-71bb-40a5-9d6b-0284db0eaa86 thread=log:pid(64871) event_description="2019-06-03 21:51:51.935 CDT [64873] LOG: listening on IPv4 address "127.0.0.1", port 60180"

2019-06-04 02:51:51,936 INFO class=pg-aa136468-71bb-40a5-9d6b-0284db0eaa86 thread=log:pid(64871) event_description="2019-06-03 21:51:51.936 CDT [64873] LOG: listening on Unix socket "/tmp/.s.PGSQL.60180""

......................

2019-06-04 02:51:52,010 INFO class=pg-aa136468-71bb-40a5-9d6b-0284db0eaa86 thread=log:pid(64871) event_description="server started"

2019-06-04 02:51:52,104 INFO class=EmbeddedPostgres thread=main event_description="aa136468-71bb-40a5-9d6b-0284db0eaa86 postmaster startup finished in 00:00:00.234"

2019-06-04 02:51:52,239 INFO class=init-aa136468-71bb-40a5-9d6b-0284db0eaa86:pg_ctl thread=log:pid(64884) event_description="waiting for server to shut down.... done"

2019-06-04 02:51:52,239 INFO class=EmbeddedPostgres thread=main event_description="aa136468-71bb-40a5-9d6b-0284db0eaa86 shut down postmaster in 00:00:00.110"

输出的日志虽然删除了不少内容还是占了很大篇幅,上面粗体分别是 PostgreSQL 程序安装目录,数据库目录(测试完会被删除),和启动 PostgreSQL 数据库的命令,以及启动后各种连接方式(u端口号, Unix socket 连接等)

进到 PostgreSQL 的二进制目录查看到该组件 0.13.1 对应的 PostgreSQL 版本为 10.6

或者配置数据库迁移组件 FlyWay(相当于 Python 下的 SQLAlchemy)

@Rule 
public PreparedDbRule db =
    EmbeddedPostgresRules.preparedDatabase(
        FlywayPreparer.forClasspathLocation("db/my-db-schema"));

也能由 db 得到数据源。

纯手工启停 PostgreSQL

前面是用 @Rule 或 @ClassRule 来控制 PostgreSQL,到了 JUnit5 后摒弃了 @Rule 和 @ClassRule, 要迁移到 JUnit5 的 @ExtendWith,或是简单的用代码来控制

private static EmbeddedPostgres pg;
 
@BeforeClass
public static void initDb() throws IOException {
    pg = EmbeddedPostgres.builder().setCleanDataDirectory(true).start();
 
    //初始化数据库
    ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
    databasePopulator.addScripts(
        resourceLoader.getResource("schema.sql"),
        resourceLoader.getResource("data.sql"));
    databasePopulator.execute(ps.getPostgresDatabase());
}
 
@AfterClass
public static void shutdownDb() throws IOException {
    pg.close();
}

Embedded PostgreSQL Server

除了 OpenTable Embedded PostgreSQL Component 外的另一个选择,首先 Maven 项目的话加上依赖

<dependency>
    <groupId>ru.yandex.qatools.embed</groupId>
    <artifactId>postgresql-embedded</artifactId>
    <version>2.10</version> <!-- 当前版本 -->
</dependency>

它没有提供相就的 @Rule, 需要用代码控制启停

private static EmbeddedPostgres postgres;
 
@BeforeClass
public static void initDb() throws Exception {
    postgres = new EmbeddedPostgres(Version.V11_1/*, "/path/to/predefined/data/directory"*/);
    String url = postgres.start();
 
    Connection conn = DriverManager.getConnection(url);
    //.....
}
 
@AfterClass
public static void shutdonwDb() {
    postgres.stop();
}

启动时可以选择数据文件的目录或用默认的目录,由 EmbeddedPostgres 启动数据库后可获得  JDBC 连接字符串,不能直接得到数据源。它有多个 PostgreSQL 版本可选,9.5, 9.6, 10.6, 11.1 可选。它唯有一个长处是由 EmbeddedPostgres 可直捣 PostgreSQL 的数据库进程,从而进行某些 postgres 命令能进行的操作,见下图

Java 与'嵌入式' PostgreSQL 数据库的单元测试

也来窥探一下它的启停过程

Download Version{11.1-1}:OS_X:B64 START

Download Version{11.1-1}:OS_X:B64 DownloadSize: 242187339

Download Version{11.1-1}:OS_X:B64 0% 1% 2% 3% 4%............ 97% 98% 99% 100% Download Version{11.1-1}:OS_X:B64 downloaded with 3331kb/s

Download Version{11.1-1}:OS_X:B64 DONE

Extract /Users/yanbin/.embedpostgresql/postgresql-11.1-1-osx-binaries.zip START

........................................................................................Extract /Users/yanbin/.embedpostgresql/postgresql-11.1-1-osx-binaries.zip DONE

2019-06-04 03:52:43,645 INFO class=Executable thread=main event_description="start AbstractPostgresConfig{storage=Storage{dbDir= /var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/postgresql-embed-821b4ee9-ccaa-45b1-a1b2-dfa6e0359869/db-content-0e69f370-ac09-4b33-be86-e3097670189f , dbName='postgres', isTmpDir=true}, network=Net{host='localhost', port=61845 }, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{username='postgres', password='postgres'}, args=[], additionalInitDbParams=[-E, SQL_ASCII, --locale=C, --lc-collate=C, --lc-ctype=C]}"

2019-06-04 03:52:44,516 INFO class=Executable thread=main event_description="start AbstractPostgresConfig{storage=Storage{dbDir=/var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/postgresql-embed-821b4ee9-ccaa-45b1-a1b2-dfa6e0359869/db-content-0e69f370-ac09-4b33-be86-e3097670189f, dbName='postgres', isTmpDir=true}, network=Net{host='localhost', port=61845}, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{username='postgres', password='postgres'}, args=[postgres], additionalInitDbParams=[]}"

2019-06-04 03:52:44,542 INFO class=Executable thread=main event_description="start AbstractPostgresConfig{storage=Storage{dbDir=/var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/postgresql-embed-821b4ee9-ccaa-45b1-a1b2-dfa6e0359869/db-content-0e69f370-ac09-4b33-be86-e3097670189f, dbName='postgres', isTmpDir=true}, network=Net{host='localhost', port=61845}, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{username='postgres', password='postgres'}, args=[], additionalInitDbParams=[-E, SQL_ASCII, --locale=C, --lc-collate=C, --lc-ctype=C]}"

2019-06-04 03:52:44,664 INFO class=PostgresProcess thread=main event_description="trying to stop postgresql"

2019-06-04 03:52:44,741 INFO class=Executable thread=main event_description="start AbstractPostgresConfig{storage=Storage{dbDir=/var/folders/xz/vqv039517flcxtqzrq_jjy1xqzfzc0/T/postgresql-embed-821b4ee9-ccaa-45b1-a1b2-dfa6e0359869/db-content-0e69f370-ac09-4b33-be86-e3097670189f, dbName='postgres', isTmpDir=true}, network=Net{host='localhost', port=61845}, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{username='postgres', password='postgres'}, args=[stop], additionalInitDbParams=[]}"

2019-06-04 03:52:44,868 INFO class=ProcessControl thread=main event_description="execSuccess: false [kill, 67796]"

第一次运行需要下载相应版本的 PostgreSQL 二进制包(240 多M),临时目录 ~/.embedpostgresql/ 下有则无需下载,由以上日志也能看出实现原理与 OpenTable Embedded PostgreSQL Component 基本是一样的。

选择哪一个测试组件?

以下是两个组件的对比

OpenTable Embedded PostgreSQL

  1. 提供了多种初始化方式, @Rule, FlyWay, Liquibase
  2. 提供了多种数据连接方式,TCP/IP, 本地 Socket 等
  3. 直接获得数据源
  4. 当前 PostgreSQL 是 10.7, 二进制文件安装后总尺寸不到 100M

Embedded PostgreSQL Server

  1. 只能手动启动,停止 PostgreSQL
  2. 只能直接获得 JDBC 连接字符串,自己创立连接或数据源
  3. 提供多个 PostgreSQL 版本的选择(意义可能不大)
  4. 下载的二进制安装文件很大,压缩包都有 PostgreSQL 11.1 -- 240 多M, PostgreSQL 10.6 -- 280 多M

综上对比本人还是会选择 OpenTable Embedded PostgreSQL,再其次可能是 Testcontainers + PostgreSQL 模块。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JSF第一步

JSF第一步

罗会波 / 清华大学出版社 / 2007-10 / 65.00元

《JSF第一步:JSF+Spring+Hibernate+AJAX编程》讲述JSF是表示层框架的标准,Hibernate是一个比较完善的对象关系映射工具,Spring则提供了一个Web应用的轻量级的解决方案。在开发一个多层的Java EE应用程序时,这些框架可谓是相辅相成、相得益彰,可以称得上是开发轻量级Java EE应用的三剑客。另外,AJAX是一种非常流行的改善用户体验的技术,但目前国内外还没......一起来看看 《JSF第一步》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具