You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在每次测试后将PostgreSQL数据库恢复至基准状态?

PostgreSQL测试容器状态快照与恢复方案

完全可行,你可以通过PostgreSQL的基准备份恢复或者模板数据库复制实现高效的状态重置,既解决提交后无法回滚的问题,又比重建数据库/手动写恢复SQL更高效易维护。以下是具体实现方案:

方案一:基准SQL备份+测试前恢复

适合数据量不大的场景,操作简单直接:

1. 会话级容器初始化(全局夹具)

启动PostgreSQL容器后,先构建好仅含admin行的基准状态,然后导出备份文件:

import pytest
from testcontainers.postgres import PostgresContainer
from sqlalchemy import create_engine, text

@pytest.fixture(scope="session")
def postgres_container():
    with PostgresContainer("postgres:15") as container:
        # 连接默认数据库,初始化基准表和数据
        engine = create_engine(container.get_connection_url())
        with engine.connect() as conn:
            # 创建users表
            conn.execute(text("""
                CREATE TABLE users (
                    id SERIAL PRIMARY KEY,
                    username VARCHAR(50) UNIQUE NOT NULL
                )
            """))
            # 插入admin基准行
            conn.execute(text("INSERT INTO users (username) VALUES ('admin')"))
            conn.commit()
        
        # 导出基准状态到容器内的备份文件
        container.exec(f"pg_dump -U {container.username} -d {container.dbname} -f /tmp/base_backup.sql")
        yield container

2. 函数级状态重置(测试夹具)

每个测试执行前,从备份文件恢复基准状态:

@pytest.fixture(scope="function")
def fresh_db(postgres_container):
    # 恢复基准备份
    postgres_container.exec(f"psql -U {postgres_container.username} -d {postgres_container.dbname} -f /tmp/base_backup.sql")
    # 返回SQLAlchemy连接供测试使用
    engine = create_engine(postgres_container.get_connection_url())
    with engine.connect() as conn:
        yield conn

3. 测试场景实现

每个测试直接使用fresh_db夹具,自动基于基准状态运行:

def test_only_admin(fresh_db):
    result = fresh_db.execute(text("SELECT username FROM users")).fetchall()
    assert len(result) == 1
    assert result[0][0] == "admin"

def test_admin_and_user_foo(fresh_db):
    # 添加测试数据
    fresh_db.execute(text("INSERT INTO users (username) VALUES ('user_foo')"))
    fresh_db.commit()
    # 验证状态
    usernames = [row[0] for row in fresh_db.execute(text("SELECT username FROM users")).fetchall()]
    assert len(usernames) == 2
    assert {"admin", "user_foo"} == set(usernames)

def test_admin_and_three_users(fresh_db):
    test_users = ["user_x", "user_y", "user_z"]
    for user in test_users:
        fresh_db.execute(text("INSERT INTO users (username) VALUES (:user)"), {"user": user})
    fresh_db.commit()
    # 验证状态
    usernames = [row[0] for row in fresh_db.execute(text("SELECT username FROM users")).fetchall()]
    assert len(usernames) == 4
    assert "admin" in usernames
    assert set(test_users).issubset(usernames)

方案二:模板数据库复制(更高效)

如果你的测试涉及较多表或数据,推荐用PostgreSQL的模板数据库特性——它是文件级复制,速度远快于执行SQL备份:

1. 会话级模板初始化

创建基准数据库并标记为模板:

import pytest
import os
from testcontainers.postgres import PostgresContainer
from sqlalchemy import create_engine, text

@pytest.fixture(scope="session")
def postgres_container():
    with PostgresContainer("postgres:15") as container:
        # 创建基准数据库
        engine = create_engine(container.get_connection_url())
        with engine.connect() as conn:
            conn.execute(text("CREATE DATABASE base_db"))
            conn.commit()
        
        # 初始化基准数据库的表和数据
        base_engine = create_engine(container.get_connection_url(dbname="base_db"))
        with base_engine.connect() as conn:
            conn.execute(text("""
                CREATE TABLE users (
                    id SERIAL PRIMARY KEY,
                    username VARCHAR(50) UNIQUE NOT NULL
                )
            """))
            conn.execute(text("INSERT INTO users (username) VALUES ('admin')"))
            conn.commit()
        
        # 将基准数据库设置为模板
        container.exec(f"psql -U {container.username} -d postgres -c 'ALTER DATABASE base_db IS_TEMPLATE = TRUE;'")
        yield container

2. 函数级测试数据库创建

每个测试前从模板复制新数据库,测试后销毁:

@pytest.fixture(scope="function")
def test_db(postgres_container):
    # 生成唯一测试数据库名
    test_db_name = f"test_db_{hash(os.urandom(4))}"
    # 从模板复制新数据库
    postgres_container.exec(f"psql -U {postgres_container.username} -d postgres -c 'CREATE DATABASE {test_db_name} TEMPLATE base_db;'")
    # 返回测试数据库连接
    engine = create_engine(postgres_container.get_connection_url(dbname=test_db_name))
    with engine.connect() as conn:
        yield conn
    # 测试结束后清理数据库
    postgres_container.exec(f"psql -U {postgres_container.username} -d postgres -c 'DROP DATABASE {test_db_name};'")

3. 测试场景实现

和方案一的测试代码完全一致,只需把fresh_db换成test_db即可。

方案对比

  • 备份恢复:实现简单,适合小型测试场景;恢复速度取决于SQL脚本大小。
  • 模板复制:速度极快(毫秒级),适合表多、数据量大的场景;需要额外管理模板数据库。

这两种方案都能完美满足你的需求:每个测试都基于纯净的基准状态运行,测试后的变更不会影响其他测试,且无需手动维护恢复逻辑。

内容的提问来源于stack exchange,提问作者Vito De Tullio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:13:19