如何实现测试环境隔离&临时数据库(pytest+SQLite)

发布于:2025-09-16 ⋅ 阅读:(39) ⋅ 点赞:(0)

目录

一、为什么需要隔离测试环境?

问题场景想象:

核心原则:

二、什么是临时数据库?

三、常见的临时数据库方案

方案 1:SQLite 内存数据库(最简单)

方案 2:Docker 临时容器(更真实)

方案 3:Python 库创建临时文件(SQLite)

四、如何确保每次测试的独立性?

方法 1:事务回滚(推荐)

方法 2:手动清空表

五、完整测试案例演示

测试场景:

测试代码:

六、关键总结



一、为什么需要隔离测试环境?

问题场景想象:

假设你正在测试一个“删除用户”的接口:

  1. 你在 ​​生产环境​​ 的数据库中测试,不小心删除了真实用户数据。
  2. 老板打电话质问为什么客户账号消失了 😨

这就是没有隔离的灾难性后果!

核心原则:

​测试代码绝不能污染真实数据​​。必须用一个“沙盒”环境,测试后能完全复原。


二、什么是临时数据库?

临时数据库是 ​​专为测试临时创建​​ 的数据库,特点:

  1. ​独立于生产库​​:和真实业务数据完全隔离。
  2. ​用完即弃​​:测试结束后自动销毁,不留痕迹。
  3. ​快速重置​​:每次测试前都是全新的状态。

三、常见的临时数据库方案

方案 1:SQLite 内存数据库(最简单)
  • ​适用场景​​:快速运行单元测试,无需复杂配置。
  • ​原理​​:数据库仅存在于内存中,程序退出后自动消失。
  • ​配置示例(Flask + SQLAlchemy)​​:
    # tests/conftest.py
    import pytest
    from app import create_app, db
    
    @pytest.fixture
    def app():
        app = create_app()
        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'  # 关键行
        app.config['TESTING'] = True
        with app.app_context():
            db.create_all()  # 创建所有表
        yield app
        db.session.remove()  # 清理
  • ​优点​​:极快,零配置。
  • ​缺点​​:不支持某些数据库特有功能(如 PostgreSQL 的 JSONB 类型)。
方案 2:Docker 临时容器(更真实)
  • ​适用场景​​:需要模拟和生产完全一致的数据库(如 MySQL/PostgreSQL)。
  • ​原理​​:用 Docker 启动一个临时数据库容器,测试后销毁。
  • ​操作步骤​​:
    1. 安装 Docker。
    2. 在测试前启动容器:
      # 启动一个临时 PostgreSQL(测试后自动删除)
      docker run --rm -d -p 5432:5432 \
        -e POSTGRES_PASSWORD=testpass \
        --name test_db postgres:13
    3. 在 Flask 测试配置中连接这个容器:
      app.config['SQLALCHEMY_DATABASE_URI'] = \
        'postgresql://postgres:testpass@localhost:5432/postgres'
    4. 测试结束后,Docker 会自动删除容器(--rm 参数作用)。
  • ​优点​​:完全模拟生产环境。
  • ​缺点​​:需要 Docker 环境,启动稍慢。
方案 3:Python 库创建临时文件(SQLite)
  • ​适用场景​​:不想用内存库,又需要文件型数据库。
  • ​示例​​:
    import tempfile
    from pathlib import Path
    
    # 创建一个临时数据库文件
    temp_db = Path(tempfile.mkdtemp()) / "test.db"
    app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{temp_db}'
    
    # 测试结束后手动删除
    temp_db.unlink()

四、如何确保每次测试的独立性?

即使使用临时数据库,也需保证 ​​每个测试用例的数据不互相干扰​​。常用方法:

方法 1:事务回滚(推荐)
@pytest.fixture(autouse=True)
def db_session(app):
    with app.app_context():
        db.session.begin_nested()  # 开启嵌套事务
        yield
        db.session.rollback()      # 自动回滚
  • ​效果​​:每个测试结束后,所有数据库操作会被撤销。
方法 2:手动清空表
@pytest.fixture
def clean_db(app):
    with app.app_context():
        for table in reversed(db.metadata.sorted_tables):
            db.session.execute(table.delete())
        db.session.commit()

五、完整测试案例演示

测试场景:

验证文件上传接口的 MD5 冲突检测(若文件已存在在DB中 return409 错误;反之发送请求)。

测试代码:
# tests/test_upload.py
def test_duplicate_file(client, clean_db):
    # 1. 预置一条“已存在”的文件记录
    existing_file = FileRecord(md5="fake_md5", filename="test.txt")
    db.session.add(existing_file)
    db.session.commit()

    # 2. 模拟上传相同 MD5 的文件
    test_file = io.BytesIO(b"test content")
    test_file.filename = "duplicate.txt"

    response = client.post(
        "/upload",
        data={"file": (test_file, test_file.filename)},
        content_type="multipart/form-data"
    )

    # 3. 验证返回 409
    assert response.status_code == 409
    assert "already exists" in response.json["error"]

六、关键总结

  1. ​临时数据库的本质​​:

    • 测试时用的“一次性”数据库,可以是:
      • 内存数据库(sqlite:///:memory:
      • Docker 临时容器
      • 临时文件数据库
  2. ​隔离的核心目标​​:

    • ​不影响生产数据​
    • ​测试用例之间不互相干扰​
  3. ​选择建议​​:

    • 简单项目 → SQLite 内存库
    • 复杂项目 → Docker 模拟生产数据库
  4. ​永远记住​​:

    # 错误示范!绝对不要这样连接生产库!
    app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://prod_user:password@prod-db.com:3306/prod_db'

通过这种隔离,你可以放心大胆地测试“删除”、“更新”等危险操作,而不用担心搞砸真实数据