138

对于我编写的每个单元测试,我想运行一个仅在内存中运行的小型 PostgreSQL 数据库。例如:

@Before
void setUp() {
    String port = runPostgresOnRandomPort();
    connectTo("postgres://localhost:"+port+"/in_memory_db");
    // ...
}

理想情况下,我会将一个 postgres 可执行文件签入版本控制,单元测试将使用它。

类似的东西HSQL,但对于 postgres。我怎样才能做到这一点?

我能得到这样的 Postgres 版本吗?如何指示它不使用磁盘?

4

10 回答 10

99

(将我的答案从Using in-memory PostgreSQL 中移出并对其进行概括):

你不能在进程中运行 Pg,在内存中

我不知道如何运行内存中的 Postgres 数据库进行测试。可能吗?

不,这是不可能的。PostgreSQL 用 C 实现并编译为平台代码。与 H2 或 Derby 不同,您不能只加载jar并启动它作为一次性内存数据库。

与同样用 C 编写并编译为平台代码的 SQLite 不同,PostgreSQL 也不能在进程内加载。它需要多个进程(每个连接一个),因为它是多处理架构,而不是多线程架构。多处理要求意味着您必须将 postmaster 作为独立进程启动。

而是:预配置连接

我建议简单地编写测试以期望特定的主机名/用户名/密码可以工作,并让测试工具CREATE DATABASE成为一次性数据库,然后DROP DATABASE在运行结束时。从属性文件、构建目标属性、环境变量等获取数据库连接详细信息。

只要您提供给单元测试的用户不是超级用户,而是拥有CREATEDB权限的用户,就可以安全地使用您已经拥有您关心的数据库的现有 PostgreSQL 实例。在最坏的情况下,您会在其他数据库中产生性能问题。出于这个原因,我更喜欢运行一个完全隔离的 PostgreSQL 安装来进行测试。

而是:启动一次性 PostgreSQL 实例进行测试

或者,如果您真的很热衷,您可以让您的测试工具找到initdbpostgres二进制文件,运行initdb以创建数据库,修改pg_hba.conftrust,运行postgres以在随机端口上启动它,创建用户,创建数据库,然后运行测试。您甚至可以将多个架构的 PostgreSQL 二进制文件捆绑在一个 jar 中,并在运行测试之前将当前架构的二进制文件解压缩到一个临时目录。

我个人认为这是一个应该避免的主要痛苦。配置测试数据库要容易得多。但是,随着对;的include_dir支持的出现,它变得更容易了。postgresql.conf现在您可以只追加一行,然后为所有其余部分编写生成的配置文件。

使用 PostgreSQL 进行更快的测试

有关如何安全地提高 PostgreSQL 性能以进行测试的更多信息,请参阅我之前在此主题上写的详细答案:优化 PostgreSQL 以进行快速测试

H2 的 PostgreSQL 方言不是真正的替代品

有些人转而使用 PostgreSQL 方言模式下的 H2 数据库来运行测试。我认为这几乎与使用 SQLite 进行测试和使用 PostgreSQL 进行生产部署的 Rails 人员一样糟糕。

H2 支持一些 PostgreSQL 扩展并模拟 PostgreSQL 方言。然而,它只是 - 一个仿真。您会发现 H2 接受查询但 PostgreSQL 不接受查询的区域、行为不同的区域等。在撰写本文时,您还会发现 PostgreSQL 支持做一些 H2 不能做的事情的很多地方——比如窗口函数。

如果您了解这种方法的局限性并且您的数据库访问很简单,那么 H2 可能就可以了。但是在这种情况下,您可能更适合使用抽象数据库的 ORM,因为您无论如何都不会使用它的有趣功能 - 在这种情况下,您不必再关心数据库的兼容性。

表空间不是答案!

不要使用表空间来创建“内存”数据库。它不仅没有必要,因为它无论如何都不会显着提高性能,而且它也是一种很好的方式来中断对同一 PostgreSQL 安装中您可能关心的任何其他内容的访问。9.4 文档现在包含以下警告

警告

即使位于主 PostgreSQL 数据目录之外,表空间也是数据库集群的一个组成部分,不能被视为数据文件的自治集合。它们依赖于主数据目录中包含的元数据,因此不能附加到不同的数据库集群或单独备份。同样,如果您丢失了表空间(文件删除、磁盘故障等),数据库集群可能会变得不可读或无法启动。将表空间放置在像 ramdisk 这样的临时文件系统上会危及整个集群的可靠性。

因为我注意到有太多人这样做并遇到了麻烦。

(如果你已经这样做了,你可以mkdir丢失表空间目录来让 PostgreSQL 重新启动,然后DROP是丢失的数据库、表等。最好不要这样做。)

于 2014-06-16T00:53:52.980 回答
70

或者您可以TABLESPACE在 ramfs / tempfs 中创建一个并在那里创建所有对象。
最近有人向我指出一篇关于在 Linux 上执行此操作的文章。原始链接已失效。但它已存档(由 Arsinclair 提供):

警告

这会危及整个数据库集群的完整性。
阅读手册中添加的警告。
因此,这只是可消耗数据的一种选择。

对于单元测试,它应该可以正常工作。如果您在同一台机器上运行其他数据库,请务必使用单独的数据库集群(有自己的端口)以确保安全。

于 2011-10-24T08:08:48.607 回答
55

这在 Postgres 中是不可能的。它不提供像 HSQLDB 或 MySQL 这样的进程内/内存引擎。

如果您想创建一个独立的环境,您可以将 Postgres 二进制文件放入 SVN(但它不仅仅是一个可执行文件)。

您需要先运行initdb来设置您的测试数据库,然后才能对此进行任何操作。这可以通过批处理文件或使用 Runtime.exec() 来完成。但请注意,initdb 不是很快的东西。您绝对不想为每个测试都运行它。不过,您可能会在您的测试套件之前运行它。

但是,虽然可以这样做,但我建议您安装一个专用的 Postgres,您只需在运行测试之前重新创建您的测试数据库。

您可以使用模板数据库重新创建测试数据库,这使得创建它的速度非常快(为每次测试运行运行 initdb 快得多)

于 2011-10-24T08:20:13.953 回答
38

现在可以通过 OpenTable 中的嵌入式 PostgreSQL 组件在 JUnit 测试中运行 PostgreSQL 的内存实例:https ://github.com/opentable/otj-pg-embedded 。

通过将依赖项添加到 otj-pg-embedded 库 ( https://mvnrepository.com/artifact/com.opentable.components/otj-pg-embedded ),您可以在 @Before 和@Afer 钩子:

EmbeddedPostgres pg = EmbeddedPostgres.start();

他们甚至提供了一个 JUnit 规则来自动让 JUnit 为您启动和停止 PostgreSQL 数据库服务器:

@Rule
public SingleInstancePostgresRule pg = EmbeddedPostgresRules.singleInstance();
于 2017-09-21T11:18:08.513 回答
18

您可以使用TestContainers启动 PosgreSQL docker 容器进行测试: http ://testcontainers.viewdocs.io/testcontainers-java/usage/database_containers/

TestContainers 提供了一个JUnit @Rule/@ClassRule:此模式在测试之前在容器内启动一个数据库,然后将其拆除。

例子:

public class SimplePostgreSQLTest {

    @Rule
    public PostgreSQLContainer postgres = new PostgreSQLContainer();

    @Test
    public void testSimple() throws SQLException {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(postgres.getJdbcUrl());
        hikariConfig.setUsername(postgres.getUsername());
        hikariConfig.setPassword(postgres.getPassword());

        HikariDataSource ds = new HikariDataSource(hikariConfig);
        Statement statement = ds.getConnection().createStatement();
        statement.execute("SELECT 1");
        ResultSet resultSet = statement.getResultSet();

        resultSet.next();
        int resultSetInt = resultSet.getInt(1);
        assertEquals("A basic SELECT query succeeds", 1, resultSetInt);
    }
}
于 2017-09-09T09:09:52.337 回答
9

现在有来自俄罗斯搜索公司 Yandex 的 PostgreSQL 内存版本:https ://github.com/yandex-qatools/postgresql-embedded

它基于 Flapdoodle OSS 的嵌入过程。

使用示例(来自github页面):

// starting Postgres
final EmbeddedPostgres postgres = new EmbeddedPostgres(V9_6);
// predefined data directory
// final EmbeddedPostgres postgres = new EmbeddedPostgres(V9_6, "/path/to/predefined/data/directory");
final String url = postgres.start("localhost", 5432, "dbName", "userName", "password");

// connecting to a running Postgres and feeding up the database
final Connection conn = DriverManager.getConnection(url);
conn.createStatement().execute("CREATE TABLE films (code char(5));");

我用了一段时间。它运作良好。

更新:这个项目不再被积极维护

Please be adviced that the main maintainer of this project has successfuly 
migrated to the use of Test Containers project. This is the best possible 
alternative nowadays.
于 2018-12-24T10:47:20.480 回答
7

如果您使用的是 NodeJS,则可以使用pg-mem(免责声明:我是作者)来模拟 postgres 数据库的最常见功能。

您将拥有一个完整的内存中、隔离的、与平台无关的数据库来复制 PG 行为(它甚至可以在浏览器中运行)。

我在这里写了一篇文章来展示如何将它用于您的单元测试。

于 2020-08-03T15:21:07.667 回答
3

您还可以使用 PostgreSQL 配置设置(例如问题中详述的设置和此处接受的答案)来实现性能,而不必求助于内存数据库。

于 2013-06-10T20:45:18.980 回答
3

如果你可以使用 docker,你可以在内存中挂载 postgresql 数据目录进行测试

docker run --tmpfs=/data -e PGDATA=/data postgres
于 2021-05-12T19:23:50.287 回答
0

如果您使用的是 java,我见过一个有效使用的库,它提供了一个内存中的“嵌入式”postgres 环境,主要用于单元测试。

https://github.com/opentable/otj-pg-embedded

如果您来此搜索结果寻找答案,这可能能够解决您的用例。

于 2021-12-16T19:02:49.913 回答