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

如何从独立Poetry包中加载pytest fixtures?

问题描述

我正尝试将部分测试配置迁移至公共Poetry包,避免重复编写相同的fixtures。现有user-service项目,在user-service/tests/conftest.py中尝试加载公共包的fixtures,代码如下:

import pytest

from httpx import AsyncClient, ASGITransport
from sqlalchemy.ext.asyncio import AsyncSession

from user_service.main import app
from common.core.database import get_db
from common.tests.db_conf import (  # noqa: F401
    anyio_backend,
    test_engine,
    setup_db,
    db_session,
)

pytest_plugins = ["common.tests.db_conf"]

# Setup http client
@pytest.fixture
async def client(db_session: AsyncSession):
    async def override_get_db():
        yield db_session

    app.dependency_overrides[get_db] = override_get_db

    async with AsyncClient(
        transport=ASGITransport(app=app), base_url="http://users_test"
    ) as ac:
        yield ac

    app.dependency_overrides.clear()

公共包common的common/common/tests/db_conf.py代码如下:

import os

import pytest
from sqlalchemy.ext.asyncio import (
    AsyncSession,
    create_async_engine,
    async_sessionmaker,
    AsyncEngine,
)
from sqlalchemy import NullPool
from sqlalchemy.engine import URL

from common.models.base import Model

pytest_plugins = ["anyio"]


@pytest.fixture(scope="session")
def anyio_backend():
    return "asyncio"


# Setup test database engine
@pytest.fixture(scope="session")
def test_engine():
    DATABASE_URL = URL.create(
        drivername="postgresql+psycopg",
        username=os.getenv("POSTGRES_USER"),
        password=os.getenv("POSTGRES_PASSWORD"),
        host=os.getenv("POSTGRES_HOST"),
        port=os.getenv("POSTGRES_PORT"),
        database=os.getenv("POSTGRES_DB"),
    )
    engine = create_async_engine(DATABASE_URL, poolclass=NullPool)
    return engine


# Setup test database
@pytest.fixture(scope="session")
async def setup_db(test_engine: AsyncEngine):
    async with test_engine.begin() as conn:
        await conn.run_sync(Model.metadata.create_all)

    yield

    async with test_engine.begin() as conn:
        await conn.run_sync(Model.metadata.drop_all)

    await test_engine.dispose()


# Setup test database session
@pytest.fixture
async def db_session(test_engine: AsyncEngine, setup_db):
    conn = await test_engine.connect()
    transaction = await conn.begin()

    test_async_session = async_sessionmaker(
        bind=conn,
        class_=AsyncSession,
        expire_on_commit=False,
        join_transaction_mode="create_savepoint",  # data is not really saved in database so that tests are isolated
    )

    async with test_async_session() as session:
        try:
            yield session
        finally:
            await session.close()
            await transaction.rollback()
            await conn.close()

user-service/pyproject.toml已配置引入common包:

[tool.poetry]
name = "user-service"
version = "0.1.0"
description = ""
authors = ["Misha4231 <misha@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.136.1"
ruff = "^0.15.12"
sqlalchemy = "^2.0.49"
pydantic-settings = "^2.14.0"
uvicorn = "^0.46.0"
alembic = "^1.18.4"
psycopg2-binary = "^2.9.12"
asyncpg = "^0.31.0"
structlog = "^25.5.0"
pytest = "^9.0.3"
pytest-asyncio = "^1.3.0"
pytest-mock = "^3.15.1"
httpx = "^0.28.1"
psycopg = "^3.3.3"
anyio = "^4.13.0"
common = "^0.1.1"

[tool.poetry.group.local.dependencies]
common = { path = "../common", develop = true }

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
pythonpath = ["..", "."]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

使用Docker Compose运行测试:

docker-compose -f docker-compose-test.yml run --rm users_test

但每个测试都报错:

E       fixture 'db_session' not found
>       available fixtures: _class_scoped_runner, _function_scoped_runner, _module_scoped_runner, _package_scoped_runner, _session_scoped_runner, anyio_backend, anyio_backend_name, anyio_backend_options, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, capteesys, class_mocker, client, doctest_namespace, event_loop_policy, free_tcp_port, free_tcp_port_factory, free_udp_port, free_udp_port_factory, mocker, module_mocker, monkeypatch, package_mocker, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, session_mocker, subtests, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory
>       use 'pytest --fixtures [testpath]' for help on them.

请问如何从另一个Poetry包中加载pytest fixtures?

解决方案

1. 确保公共包的测试模块被正确打包

Poetry默认不会打包tests目录下的内容,导致common/tests/db_conf.py无法被包含在安装的包中,pytest自然找不到该模块。

修改common项目的pyproject.toml,添加include配置,明确将需要共享的fixture文件打包:

[tool.poetry]
# 保留原有配置
include = [
    "common/**/*",
    "common/tests/db_conf.py"  # 指定共享的fixture文件
]

如果需要共享整个测试目录,可改为:

include = [
    "common/**/*",
    "common/tests/**/*"
]

2. 验证pytest插件路径

确认pytest_plugins = ["common.tests.db_conf"]的模块路径正确。由于common是根包,tests/db_conf.py在其目录下,该路径是正确的,但需确保文件已被打包并能被Python导入。

3. 检查Docker环境的依赖安装

你在user-service中用了group.local的依赖配置,需确保Docker环境中安装的是本地开发版本的common包。在Dockerfile中执行poetry install --with local,这样才能加载本地修改后的common包内容。

4. 移除冗余导入

user-service/tests/conftest.py中导入common.tests.db_conf内fixture的代码是多余的,只要通过pytest_plugins声明插件,pytest会自动加载该模块的fixture,可删除这部分代码:

# 删除以下代码
from common.tests.db_conf import (  # noqa: F401
    anyio_backend,
    test_engine,
    setup_db,
    db_session,
)

5. 测试验证

修改完成后,先在本地运行pytest --fixtures,查看是否能列出db_session等来自common包的fixture,确认加载成功后再在Docker环境中重新构建镜像并运行测试。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 20:34:54