使用pytest时出现“找不到模块”的错误

3
我有一个使用Python 3的虚拟环境,并且目前正在使用pytest进行测试。我已经打包了一个名为“process_expiring_passwords”的程序,并从名为“pep.py”的通用脚本文件中导入此包。我还在我的虚拟环境中使用pip安装了我的process_expiring_passwords包。
然而,当我去运行我的测试时,却出现了以下错误:
ImportError while importing test module '/home/username/projects/process-expiring-passwords/tests/test_process_expiring_passwords.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: tests/test_process_expiring_passwords.py:3: in from process_expiring_passwords import ProcessExpiringPasswords process_expiring_passwords/process_expiring_passwords.py:16: in from process_expiring_passwords.models.ProcessExpiringPasswordsLog ModuleNotFoundError: No module named 'process_expiring_passwords.models'; 'process_expiring_passwords' is not a package 是否有特定原因使得我可以从我的pep.py通用脚本文件中使用我的包,而pytest无法找到该包?
与此问题相关的代码的关键部分如下: pep.py:
from process_expiring_passwords.process_expiring_passwords \
    import ProcessExpiringPasswords

pep = ProcessExpiringPasswords()
pep.process()

process_expiring_passwords/process_expiring_passwords.py:

from process_expiring_passwords.models.ProcessExpiringPasswordsLog \
    import ProcessExpiringPasswordsLog

class ProcessExpiringPasswords:
    def __init__(self):
        self.example = 0
    def process():
        pepl = ProcessExpiringPasswordsLog()

process_expiring_passwords/models/ProcessExpiringPasswordsLog.py:

class ProcessExpiringPasswordsLog():
    def __init__(self):
        self.example = 0

process_expiring_passwords/setup.py:

# ref: https://github.com/pypa/sampleproject/blob/master/setup.py
# ref: http://python-packaging.readthedocs.io/en/latest/minimal.html
from setuptools import setup

setup(
    name='process_expiring_passwords',
    version='0.0.1',
    packages=find_packages(),
    description='Process expiring passwords',
    license='MIT',
    install_requires=[
        'apiclient',
        'google-api-python-client',
        'httplib2',
        'mysqlclient',
        'oauth2client',
        'requests',
        'SQLAlchemy',
    ],
)

tests/test_process_expiring_passwords.py:

from process_expiring_passwords import ProcessExpiringPasswords

def test_pep():
    pep = ProcessExpiringPasswords()
    assert pep is not None

项目结构:

process_expiring_passwords
    pep.py
    process_expiring_passwords
        __init__.py
        models
            __init__.py
            ProcessExpiringPasswordsLog.py
        process_expiring_passwords.py
        setup.py
    tests
        test_process_expiring_passwords.py

注:

  • 根据Matt Messersmith的反馈,更新了代码示例和setup.py文件。

1
pytest 找不到你的模块的唯一原因是它不在你的 PATH 中。正如你所说,你已经安装了它(我猜测是用 pip -e .),我能想到的唯一原因是 pytest 没有安装在你的虚拟环境中,因此你运行的是“主 Python”变体,它无法在 PATH 中找到你的模块。在你的虚拟环境中安装 pytest,它应该可以工作。 - MrLeeh
@hoefling 感谢您的建议!我刚刚尝试了conftest.py方法,但是我得到的结果与之前尝试过的最后两个建议相同。我仍然会遇到类似(但缩短的)错误:Traceback: tests/test_process_expiring_passwords.py:3: in <module> from process_expiring_passwords import ProcessExpiringPasswords E ImportError: 无法导入名称'ProcessExpiringPasswords' - summea
1
忽略我的先前评论,我现在明白问题所在了。您的包与模块具有相同的名称,因此当您在process_expiring_passwords/process_expiring_passwords.py中执行import process_expiring_passwords时,您不会导入父包。在process_expiring_passwords.py中添加行import process_expiring_passwords; print(process_expiring_passwords.__file__)并验证您是否导入了模块。 - hoefling
process_expiring_passwords.py 中切换到相对导入应该可以解决这个问题:from .models.UserAccountStatus import UserAccountStatus,但我现在无法自行测试。 - hoefling
1
总体而言,包结构看起来有点凌乱,请问您能否将相关代码改进为一个 [mcve]?您的设置脚本位于 process_expiring_passwords 包中,它应该被收集和安装,那么您是如何将该包传递给 packages 列表的呢?您是否使用了 setuptools.find_packages 来实现?包和模块的命名相同可能只是一种分散注意力的复杂性,与实际错误无关。 - hoefling
显示剩余5条评论
2个回答

1
您发布的代码并不完全能够工作,我已经修改了构造函数和成员函数中的关键字self。在做了这些修改之后,我可以运行pep.py。

为什么我可以在我的pep.py常规脚本文件中使用自己的包,而pytest无法找到该包?

是的,当您处于顶层process_expiring_passwords目录时,您可以运行pep.py,因为您将得到包的“本地”导入。
测试无法运行与pytest关系不大,更多的是与您的导入语句长什么样以及您没有构建一个包有关:
from process_expiring_passwords import ProcessExpiringPasswords

您还没有创建名为process_expiring_passwords的包。您可以通过执行python setup.py bdist_wheel(或只需执行sdist并解压缩tarball)来验证此内容,解压缩wheel,并注意到不存在任何包。
通常,您需要安装软件包python setup.py install(这将在您的site-packages中放置可分发文件),并且您还必须在setup.py中命名软件包(find_packages()是您的朋友),然后您的测试应该按预期工作(或多或少)。
希望对您有所帮助。

谢谢您的回答。我还有很多要学习的地方,但我已经将 packages=find_packages() 添加到了我的 setup.py 文件中,并尝试安装该包。看起来它已经成功安装,但是我仍然无法使用这个导入语句:from process_expiring_passwords import ProcessExpiringPasswords,而不会收到这个错误: ImportError: cannot import name 'ProcessExpiringPasswords'。当我按照您的示例使用 python setup.py install 时,是否存在任何其他步骤我错过了呢? - summea
通常 setup.py 位于最高层:与 setup.py 相同目录下没有 __init__.py(这是我最熟悉的设置)。您可能可以通过不同的导入语句或更改对 setup 的调用来使其按您的方式工作,但老实说,我并不知道 setup.py 的所有细节(它有点复杂)。尝试创建一个新目录(包),也许称之为 my_package(稍后可以重命名)。将 __init__.pyprocess_ex_pass.py 移动到其中,然后尝试 from my_package.process_expiring_passwords import ProcessExpiringPasswords。希望对您有所帮助。 - Matt Messersmith

1

在对这个问题进行扩展讨论后,根据@hoefling的评论和在@Matt Messersmith的回答中添加packages=find_packages()setup.py,以下是特别有帮助的步骤:

  1. setup.py移动到项目根目录
  2. 删除__pycache__文件
  3. 在主模块文件中使用相对导入

    process_expiring_passwords/process_expiring_passwords.py:

    from .models.ProcessExpiringPasswordsLog \ import ProcessExpiringPasswordsLog

  4. 运行命令:python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"

  5. easy-install.pth中移除旧的软件包行 (应该使用上一步骤的结果路径来查找)
  6. 运行命令:pip install -e .
  7. 在测试文件的导入部分中添加process_expiring_passwords:

    from process_expiring_passwords.process_expiring_passwords \ import ProcessExpiringPasswords


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接