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

FastAPI非ORM场景下测试数据库与开发数据库切换问题

问题描述

我是一位非ORM用户,更倾向于使用原生SQL查询。在Free Code Camp学习FastAPI课程时,课程几乎围绕ORM展开,导致我在实现部分概念时遇到困难。

目前核心问题:编写接口测试时,需要让测试和CRUD操作指向测试数据库而非开发数据库。我正在使用testing.postgresql创建测试数据库,现有测试代码、数据库连接代码及配置代码如下:

现有测试代码

client = TestClient(app)

class TestUser:
    def setup_method(self, method):
        self.postgres = testing.postgresql.Postgresql(cache_initialized_db=True)
        self.db = psycopg2.connect(**self.postgres.dsn())

    def teardown_method(self, method):
        self.db.close()

    def test_get_users(self):
        res = client.get("/users")
        assert res.status_code == 200
        assert res.headers["content-type"] == "application/json"

现有数据库连接代码(未使用依赖)

while True:
    try:
        conn = psycopg2.connect(
            database=settings.database,
            user=settings.user,
            port=settings.port,
            password=settings.password,
            cursor_factory=RealDictCursor,
        )
        cr = conn.cursor()
        console.print(
            "[green bold]SUCCESS[/]:    Connection To Database Established successfuly"
        )
        break
    except psycopg2.OperationalError:
        console.print(
            "[red bold]FAILED[/]:    Connection To Database Failed , Trying Again"
        )
        time.sleep(2)

现有配置代码

load_dotenv()

class Settings(BaseSettings):
    database: str
    user: str
    port: int
    password: str
    expiray: int
    algorithm: str
    secret_key: str

settings = Settings()

需要解决:如何让测试和CRUD操作指向测试数据库而非开发数据库?需要添加哪些关联代码?


解决方案

1. 改用FastAPI依赖注入管理数据库连接

将硬编码的数据库连接改为FastAPI依赖,这样测试时可轻松替换连接对象:

创建dependencies.py文件(或直接写入主文件):

from fastapi import Depends
import psycopg2
from psycopg2.extras import RealDictCursor
from .config import settings

def get_db_connection():
    conn = None
    try:
        conn = psycopg2.connect(
            database=settings.database,
            user=settings.user,
            port=settings.port,
            password=settings.password,
            cursor_factory=RealDictCursor,
        )
        yield conn
    finally:
        if conn:
            conn.close()

def get_db_cursor(conn=Depends(get_db_connection)):
    cur = conn.cursor()
    try:
        yield cur
    finally:
        cur.close()

在路由中使用该依赖(以/users接口为例):

from fastapi import APIRouter, Depends
from psycopg2.extras import RealDictCursor
from .dependencies import get_db_cursor

router = APIRouter(prefix="/users")

@router.get("/")
def get_users(cur: RealDictCursor = Depends(get_db_cursor)):
    cur.execute("SELECT * FROM users;")
    users = cur.fetchall()
    return users

2. 调整配置,支持测试环境切换

修改Settings类,添加环境标识,方便测试时自动切换配置:

load_dotenv()

class Settings(BaseSettings):
    database: str
    user: str
    port: int
    password: str
    expiray: int
    algorithm: str
    secret_key: str
    # 默认开发环境,测试时可通过环境变量覆盖
    environment: str = "development"

    class Config:
        env_file = ".env"

settings = Settings()

3. 修改测试代码,替换数据库依赖

利用FastAPI的override_depends功能,让测试客户端使用测试数据库连接:

from fastapi.testclient import TestClient
from fastapi import Depends
import testing.postgresql
import psycopg2
from psycopg2.extras import RealDictCursor
from your_app_module import app, get_db_connection, get_db_cursor

client = TestClient(app)

class TestUser:
    def setup_method(self, method):
        # 启动测试PostgreSQL实例
        self.postgres = testing.postgresql.Postgresql(cache_initialized_db=True)
        # 创建测试数据库连接
        self.test_conn = psycopg2.connect(**self.postgres.dsn(), cursor_factory=RealDictCursor)
        
        # 初始化测试表结构(替换为你的实际表SQL)
        with self.test_conn.cursor() as cur:
            cur.execute("""
                CREATE TABLE users (
                    id SERIAL PRIMARY KEY,
                    name VARCHAR(255) NOT NULL,
                    email VARCHAR(255) UNIQUE NOT NULL
                );
            """)
            self.test_conn.commit()
        
        # 覆盖依赖,返回测试连接
        def override_get_db_connection():
            yield self.test_conn
        
        def override_get_db_cursor(conn=Depends(override_get_db_connection)):
            cur = conn.cursor()
            try:
                yield cur
            finally:
                cur.close()
        
        app.dependency_overrides[get_db_connection] = override_get_db_connection
        app.dependency_overrides[get_db_cursor] = override_get_db_cursor

    def teardown_method(self, method):
        # 清理测试资源和依赖覆盖
        self.test_conn.close()
        self.postgres.stop()
        app.dependency_overrides.clear()

    def test_get_users(self):
        # 插入测试数据
        with self.test_conn.cursor() as cur:
            cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)", ("Test User", "test@example.com"))
            self.test_conn.commit()
        
        res = client.get("/users")
        assert res.status_code == 200
        assert res.headers["content-type"] == "application/json"
        assert len(res.json()) == 1
        assert res.json()[0]["name"] == "Test User"

4. (可选)优化测试数据库初始化

如果表结构复杂,可将建表SQL写入单独文件(如tests/schema.sql),测试时读取执行:

# 在setup_method中替换初始化表的代码
with open("tests/schema.sql", "r") as f:
    schema_sql = f.read()
with self.test_conn.cursor() as cur:
    cur.execute(schema_sql)
    self.test_conn.commit()

关键说明
  • 依赖注入是核心:通过依赖管理数据库连接,才能在测试时动态替换为测试库连接
  • 测试数据库初始化:原生SQL需手动创建表结构,否则会出现表不存在的错误
  • 依赖清理:测试结束后必须清除依赖覆盖,避免影响其他测试用例

内容的提问来源于stack exchange,提问作者kareem mahlees

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 16:09:26