Appearance
背景描述
- 一个后端项目,主要是对数据库中8张表的增删改查操作。之前使用的是 sqlite ,现在数据已经迁移到远程的 postgresql 。后端做了一些修改,启动后,部分功能正常,部分功能有问题。
- 项目之前是有一些测试用例的,使用了 Pytest + TestClient 技术栈。不过它们似乎部分还是顽固地连接到了之前的 sqlite 中去了,这让我怀疑这些测试用例整体上的可靠性。
重构思路
不需要彻底重写所有测试用例,但需要系统性地重构和修复,以确保测试的可靠性、可维护性和环境隔离性。
1. 首要任务:隔离测试数据库,杜绝“连错库”
- 使用测试专用的 PostgreSQL 数据库(如
yourapp_test),而非开发或生产库。 - 在
pytest中通过 fixture + 环境变量 动态注入数据库 URL
python
# conftest.py
import os
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from your_app.db import Base
TEST_DB_URL = os.getenv("TEST_DATABASE_URL", "postgresql://user:pass@localhost/yourapp_test")
@pytest.fixture(scope="session")
def test_engine():
engine = create_engine(TEST_DB_URL)
Base.metadata.create_all(bind=engine) # 创建测试表结构
yield engine
Base.metadata.drop_all(bind=engine) # 清理
@pytest.fixture
def db_session(test_engine):
Session = sessionmaker(bind=test_engine)
session = Session()
try:
yield session
finally:
session.rollback()
session.close()- 确保所有测试文件通过 fixture 注入 session,而非直接 import 全局 session。否则容易污染主应用的数据库连接配置。
2. 重构策略:保留逻辑,替换依赖
原测试用例结构是合理的(分层:API → CRUD → Model),逻辑可复用,只需修复数据库连接和事务管理。
- 保留:测试场景、断言逻辑、边界条件(如分页、日期筛选)。
- 替换/修复:
- 所有数据库连接初始化方式;
- 事务回滚机制(确保每个测试用例后自动清理数据);
- 模型创建方式(确保使用测试 session 而非全局 session)。
3. 关键改进点
1. 统一数据库连接配置
- 主应用和测试应通过 同一套配置机制(如 Pydantic Settings 或环境变量)读取
DATABASE_URL。 - 测试时通过
pytest --override-ini=...或env TEST_DATABASE_URL=... pytest指定测试库。
2. 使用事务回滚实现测试隔离
对每个测试用例,在 setup 时开启事务,teardown 时 rollback(而非 commit),避免数据残留。示例:
python
@pytest.fixture
def clean_db(db_session):
transaction = db_session.begin()
yield db_session
transaction.rollback()3. 删除冗余测试文件
原有 9 个测试文件,存在明显重叠:
test_db_connection.py、test_db_simple.py、test_db_operations.py功能高度重复 → 合并为一个test_database.py。test_login_logout.py和test_api_with_auth.py也有重叠 → 保留后者,前者可删。test_weight_records.py功能已被test_crud.py和test_api.py覆盖 → 可删除或保留作为专项压力测试。
🧹 精简后应保留 4 类测试:
test_models.py(模型结构)test_crud.py(数据库操作)test_api.py(含认证的端到端 API 测试)test_database.py(连接与基础读写)
4. 增加 PostgreSQL 特性兼容性测试
SQLite 与 PostgreSQL 在以下方面有差异,需专项验证:
- 大小写敏感(PostgreSQL 表名/列名默认小写);
- 数据类型(如
DATETIMEvsTIMESTAMP); - 自增 ID 行为;
- 字符串比较(
ILIKEvsLIKE); - 分页语法(虽然用 ORM 可能屏蔽了,但需确认)。
4. 验证迁移是否成功:关键检查清单
| 项目 | 检查方式 |
|---|---|
| 所有测试连接的是 PostgreSQL | 在测试中打印 engine.url,确认无 sqlite |
| 每个测试用例独立、无状态 | 运行单个测试、多个测试、乱序运行,结果一致 |
| 数据在测试后自动清理 | 测试结束后查表应为空或初始状态 |
| 分页/筛选/排序结果正确 | 对比 SQLite 与 PostgreSQL 返回结果是否一致 |
| 错误处理正常(如唯一约束、非空约束) | PostgreSQL 约束更严格,需验证异常被捕获 |
5. 行动建议(分步)
- 立即:建立独立的
yourapp_testPostgreSQL 数据库。 - 修改:配置系统,支持通过环境变量切换 DB URL。
- 重构
conftest.py,引入 fixture 管理测试 DB 生命周期。 - 逐个修复测试文件,替换硬编码连接,使用 fixture 注入 session。
- 删除冗余测试文件,合并重复逻辑。
- 运行全量测试,确认:
- 所有测试通过;
- 无连接 SQLite 的日志;
- 测试数据库在运行后为空。
准备环境 - 数据库
pytest 要求连接到一个没有数据的空数据库中
用 PuTTY 连接到远程服务器
- 用 deploy 这个用户登录到远程服务器
bash
# 创建工作目录
mkdir -p ~/pg_migration
# 进入工作目录
cd ~/pg_migration
# 执行 pg_dump 命令,将开发数据库的 schema 导出到 create_schema.sql 文件中
pg_dump -h localhost -U kpfit_dev -d kpfit_dev_db --schema-only --no-owner --no-privileges -f create_schema.sql通过 FileZilla 下载 create_schema.sql 文件
- 用 deploy 这个用户,通过 FileZilla 连接到远程服务器。注意:用户名和 PuTTY 中配置的一致。
- 从远程服务器上的
~/pg_migration目录下载create_schema.sql文件到本地。
DBeaver 中执行 create_schema.sql 文件
注意:DBeaver 作为一个标准的 SQL 客户端,不认识 \ 开头的命令,所以会报语法错误。所以应先查找并删除所有以 \ 开头的行:
- 删除第一行的 \restrict ...。
- 删除最后一行的 \unrestrict ...。
用 DBeaver 连接到测试数据库(
kpfit_test_db)。打开
create_schema.sql文件,点击右键选择Run as SQL Script。确认执行后,测试数据库应包含与开发数据库相同的 schema。
测试用例重构
文件目录结构
plaintext
1 keepfit\
2 └── server\
3 └── tests\ # pytest 测试用例主目录
4 ├── __init__.py # Python 包标识
5 ├── conftest.py # pytest 配置和 fixture 定义
6 ├── test_api.py # API 接口测试
7 ├── test_crud.py # CRUD 操作测试
8 ├── test_database.py # 数据库相关测试
9 └── test_models.py # 数据模型测试测试用例开发顺序
开发顺序(自底向上)
conftest.py(工装夹具)test_database.py(验证地基)test_models.py(验证砖块)test_crud.py(验证砌墙)test_api.py(验证整栋楼)
测试用例的执行
bash
# 验证数据库连接
pytest -v tests/test_database.py
# 验证模型
pytest -v tests/test_models.py
# 验证 CRUD 操作
pytest -v tests/test_crud.py
# 验证 API 接口
pytest -v tests/test_api.py
# 执行所有测试
pytest -v --tb=short