内容简介:在我们对数据库 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 Component 和 Embedded 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 所用的端口号,它会随机选用端口号
然后进行执行自己的数据库初始化代码,创建新表,插入测试数据等等。
上面 @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
命令能进行的操作,见下图
也来窥探一下它的启停过程
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
- 提供了多种初始化方式, @Rule, FlyWay, Liquibase
- 提供了多种数据连接方式,TCP/IP, 本地 Socket 等
- 直接获得数据源
- 当前 PostgreSQL 是 10.7, 二进制文件安装后总尺寸不到 100M
Embedded PostgreSQL Server
- 只能手动启动,停止 PostgreSQL
- 只能直接获得 JDBC 连接字符串,自己创立连接或数据源
- 提供多个 PostgreSQL 版本的选择(意义可能不大)
- 下载的二进制安装文件很大,压缩包都有 PostgreSQL 11.1 -- 240 多M, PostgreSQL 10.6 -- 280 多M
综上对比本人还是会选择 OpenTable Embedded PostgreSQL,再其次可能是 Testcontainers + PostgreSQL 模块。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。