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

Python中如何导入tests子模块且能单独运行子目录测试?

问题描述

测试目录结构如下:

tests
├── __init__.py
├── conftest.py
├── data_validation
│   └── test_data_validation.py
└── per_vendor
    ├── __init__.py
    ├── vendor_a
    │   ├── conftest.py
    │   └── test_vendor_a.py
    └── vendor_b
        ├── __init__.py
        ├── conftest.py
        └── test_vendor_b.py

在tests/conftest.py中编写了以下fixture代码:

import pytest

from tests import per_vendor

@pytest.fixture
def database_with_data(empty_database: Engine) -> Engine:
    helpers = [
        per_vendor.vendor_a.conftest.fill_with_data_for_a,
        per_vendor.vendor_b.conftest.fill_with_data_for_b,
    ]
    for cur in helpers:
        cur(empty_database)
    return empty_database

其中empty_database是提供空数据库的fixture。

运行全量测试python -m pytest tests/时一切正常,但单独运行特定子目录测试(如python -m pytest tests/data_validation/)时,所有依赖database_with_data fixture的测试会报错:

AttributeError: module 'tests.per_vendor' has no attribute 'vendor_a'

已知tests.per_vendor可以被正常导入,但它的子模块无法被识别,需要了解差异原因并找到支持模块化配置且能单独运行子目录测试的解决方案。

原因分析
  • 全量测试时,pytest会扫描所有测试文件,包括per_vendor/vendor_a和per_vendor/vendor_b下的测试,这会触发对应子模块的导入,因此per_vendor模块会自动包含vendor_a、vendor_b这些子模块属性。
  • 仅运行tests/data_validation/目录的测试时,pytest不会扫描per_vendor下的测试文件,Python解释器也不会自动导入per_vendor的子模块。此时from tests import per_vendor只会导入per_vendor/__init__.py对应的模块,而不会加载其子模块,所以访问per_vendor.vendor_a会抛出属性不存在的错误。
解决方案

提供三种可行的解决思路,可根据项目结构选择:

方案1:显式导入子模块

修改tests/per_vendor/__init__.py文件,显式导入所有子模块:

from . import vendor_a
from . import vendor_b

这样无论是否运行per_vendor下的测试,只要导入per_vendor模块,就会自动加载vendor_a和vendor_b子模块,解决属性不存在的问题。

方案2:动态导入子模块

在database_with_data fixture中使用动态导入,避免依赖预加载的子模块:

import pytest
import importlib

@pytest.fixture
def database_with_data(empty_database: Engine) -> Engine:
    helper_specs = [
        ("tests.per_vendor.vendor_a.conftest", "fill_with_data_for_a"),
        ("tests.per_vendor.vendor_b.conftest", "fill_with_data_for_b")
    ]
    for module_path, func_name in helper_specs:
        mod = importlib.import_module(module_path)
        func = getattr(mod, func_name)
        func(empty_database)
    return empty_database

这种方式会在fixture执行时动态导入所需的模块和函数,不受测试运行范围的影响。

方案3:重构fixture结构(推荐)

将数据填充逻辑与vendor绑定,利用pytest的fixture自动发现机制:

  1. 在per_vendor/vendor_a/conftest.py中定义针对vendor_a的fixture:
import pytest

def fill_with_data_for_a(empty_database):
    # 原有填充逻辑
    pass

@pytest.fixture
def database_with_vendor_a_data(empty_database: Engine) -> Engine:
    fill_with_data_for_a(empty_database)
    return empty_database
  1. 在per_vendor/vendor_b/conftest.py中定义针对vendor_b的fixture:
import pytest

def fill_with_data_for_b(empty_database):
    # 原有填充逻辑
    pass

@pytest.fixture
def database_with_vendor_b_data(empty_database: Engine) -> Engine:
    fill_with_data_for_b(empty_database)
    return empty_database
  1. 在tests/conftest.py中合并这两个fixture:
import pytest

@pytest.fixture
def database_with_data(database_with_vendor_a_data, database_with_vendor_b_data) -> Engine:
    # 两个fixture已依次执行数据填充,直接返回最终数据库实例
    return database_with_vendor_a_data

这种方式更符合pytest的模块化设计,每个vendor的逻辑独立,且pytest会自动根据测试依赖加载对应的fixture,无论运行哪个目录的测试都能正常工作。

内容的提问来源于stack exchange,提问作者Max Görner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:33:19