#postgresql #diesel #testing #once-cell

diesel_pg_tester

使用实时Postgres连接高效运行Diesel测试

2次发布

0.5.1 2020年3月1日
0.5.0 2020年3月1日

#13#once-cell

MIT/Apache

19KB
229

使用实时Postgres连接高效运行diesel测试。

MIT licensed Docs


lib.rs:

使用实时Postgres连接高效运行diesel测试。

这个测试运行器利用Postgres高度事务性的特点,在每次测试之前快速将数据库恢复到已知状态。一旦数据库模式初始化完成,运行许多测试只需要几分之一秒。

示例

这个示例测试模块从一个空数据库开始。insert_and_query_user() 测试使用名为 PGTEST 的全局变量在实时Postgres事务中运行代码。函数 init_db() 可以从文件中读取模式或使用Diesel迁移初始化模式。

#[macro_use] extern crate diesel;
use diesel::connection::SimpleConnection;
use diesel::prelude::*;
use diesel::sql_types::*;
use diesel_pg_tester::DieselPgTester;
use once_cell::sync::Lazy;

// TestResult is the type that tests and init_db() return. Any error type is acceptable, but
// diesel::result::Error is convenient for this example.
type TestResult<T = ()> = Result<T, diesel::result::Error>;

// PGTEST is the global DieselPgTester that the whole test suite will use.
// The error type argument must match the TestResult error type.
static PGTEST: Lazy<DieselPgTester<diesel::result::Error>> =
    Lazy::new(|| DieselPgTester::start(1, None, init_db));

// init_db() initializes the temporary schema in the database.
fn init_db(conn: &PgConnection) -> TestResult {
    conn.batch_execute(
        "CREATE TABLE users (id BIGSERIAL PRIMARY KEY NOT NULL, name VARCHAR);")?;
    Ok(())
}

// This table! macro invocation generates a helpful submodule called users::dsl.
table! {
    users (id) {
        id -> Int8,
        name -> Varchar,
    }
}

// insert_and_query_user() is a sample test that uses PGTEST and the users::dsl submodule.
#[test]
fn insert_and_query_user() -> TestResult {
    PGTEST.run(|conn| {
        use users::dsl as U;
        diesel::insert_into(U::users).values(&U::name.eq("Quentin")).execute(conn)?;
        let user: (i64, String) = U::users.first(conn)?;
        assert_eq!("Quentin", user.1);
        Ok(())
    })
}

工作原理

测试按照以下方式运行

  • 测试套件创建一个全局的 DieselPgTester,它启动一个或多个工作线程。

  • 测试套件将测试添加到 DieselPgTester 的队列中。

  • 每个工作线程打开到Postgres的连接并开始一个事务。

  • 在事务内部,工作线程创建一个临时模式并运行测试套件提供的初始化函数以创建代码所需的表和其他对象。

  • 一旦设置好模式,工作线程轮询队列以运行测试。

  • 工作线程创建一个保存点。

  • 工作线程运行测试,将测试结果报告给排队测试的线程。

  • 测试后,工作线程恢复在测试之前创建的保存点,将模式和数据恢复到测试前的状态。

  • 工作线程重复轮询队列并运行测试,直到进程结束、测试崩溃或丢弃 DieselPgTester

  • 因为事务从未提交,数据库保持原始状态以供后续测试运行。

在开发过程中,测试经常会崩溃。当在 DieselPgTester 中运行的测试崩溃时,DieselPgTester 会选择安全的方式:断开数据库连接,事务被终止,并且工作进程检测到崩溃后启动另一个工作进程来替换自己。如果只有一个工作进程,每次崩溃后的测试都会有一个暂停,因为新的工作进程连接到数据库并重新初始化。为了避免这个暂停,测试可以选择返回 Result::Err 而不是崩溃,这样工作进程可以正常且快速地清理,而不是断开连接。

如果数据库初始化失败或导致崩溃,将停止 DieselPgTester 以避免浪费时间去运行最终会失败的测试。通过停止的 DieselPgTester 运行的所有测试都会快速失败。

使用说明

  • 在测试期间,不应该有任何东西提交事务。可以使用手动发出的 COMMIT 语句提交事务,但这样做可能会使测试套件变得不可靠。

  • 即使在 Postgres 中,一些数据库状态(如序列值)故意是非事务性的。避免使测试依赖于非事务性状态。

  • 如果数据库模式依赖于 Postgres 扩展或其他必须由数据库超级用户设置的特性,那么在运行测试之前需要设置这些特性。

  • 默认的 Postgres 模式称为 public,但 DieselPgTester 创建并使用一个临时模式而不是 public 模式,因此大多数 SQL 应该不指定模式名称。Diesel 通常不会生成带有模式名称的 SQL,但 pg_dump 工具会,因此您应该从由 pg_dump 生成的 SQL 中删除 public. 前缀(除非使用依赖于 public 模式的 Postgres 扩展。)

依赖项

~5MB
~102K SLoC