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

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

内容简介:在我们对数据库 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 模块。


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

查看所有标签

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

Head First Rails

Head First Rails

David Griffiths / O'Reilly Media / 2008-12-30 / USD 49.99

Figure its about time that you hop on the Ruby on Rails bandwagon? You've heard that it'll increase your productivity exponentially, and allow you to created full fledged web applications with minimal......一起来看看 《Head First Rails》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具