PATH 出现 pytest 的问题 'ImportError: No module named ...'

407

我在 Mac 上使用 easy_install 安装了 pytest,并开始为一个具有以下文件结构的项目编写测试:

repo/
   |--app.py
   |--settings.py
   |--models.py
   |--tests/
          |--test_app.py

repo目录中运行py.test,一切都会按照你的预期进行。
但是当我在Linux或Windows上尝试相同的操作时(两者都安装了pytest 2.2.3),每当它首次导入我的应用程序路径中的某个内容时,它就会报错。例如,from app import some_def_in_app
我需要编辑PATH来在这些系统上运行py.test吗?

4
这是使用setuptools修复它的方法。 - ederag
4
请检查@hoefling的回答,并考虑更改您接受的答案,如果SO允许在这么长时间之后:好多了! - Davide
28个回答

460

我不确定为什么py.test没有将当前目录添加到PYTHONPATH中,但这里有一个解决方法(在存储库的根目录下执行):

python -m pytest tests/

这是因为Python会自动将当前目录添加到PYTHONPATH中,所以它能够正常工作。


3
如果您要在不同级别运行应用程序的代码,则需要将相对导入重写为绝对导入。例如:project/test/all-my-testsproject/src/app.py。由于这种更改,需要通过在project/src目录中使用__main__.py文件间接调用app.py,以便可以使用python -m src命令进行调用。根据我所知道的情况,这是非常混乱的事情。 - Zelphir Kaltstahl
6
@Zelphir:使用绝对导入是一种推荐的做法。Habnabit 写了一篇关于包装最佳实践的好文章:http://blog.habnab.it/blog/2013/07/21/python-packages-and-you/,而 PEP8 则指出:“永远不应该使用隐式相对导入,并已在 Python 3 中移除。”参见:https://www.python.org/dev/peps/pep-0008/。 - Apteryx
2
@Apteryx 你是指“项目绝对路径”对吧?因为像/home/user/dev/projectxyz/src ...这样的东西通常会很糟糕,而且在其他机器上运行时也不会成功。 我想我的意思是,即使一个模块与运行文件在同一个文件夹中,我仍然必须始终将整个项目根目录写入模块路径。我不知道这被认为是最佳实践,所以这是有用的信息,谢谢。我同意大多数pep8,尽管它仍然不完美。 - Zelphir Kaltstahl
2
@Zelphir,是的,那就是我想说的。我相信在Python中,“绝对导入”这个术语总是指“项目绝对”。参见:https://www.python.org/dev/peps/pep-0328/#rationale-for-absolute-imports。事实上,我很确定你不能使用默认的“import”机制从随机的绝对路径位置导入。 - Apteryx
13
我已经在测试中添加了 __init__.py 文件,这解决了问题。现在我可以使用 pytest - Kiran Kumar Kotari
显示剩余3条评论

453

建议使用pytest>=7的方法:使用pythonpath设置

最近,pytest添加了一个新的核心插件,支持通过pythonpath配置值修改sys.path。因此,解决方案现在更简单,并且不再需要任何变通方法:

pyproject.toml示例:

[tool.pytest.ini_options]
pythonpath = [
  "."
]
< p > pytest.ini 示例:

[pytest]
pythonpath = .

路径条目是相对于rootdir计算的,因此在这种情况下, . 将添加 repo 目录到 sys.path 中。同时也可以允许多个路径条目: 对于一个布局。
repo/
├── src/
|   └── lib.py
├── app.py
└── tests
     ├── test_app.py
     └── test_lib.py

配置

[tool.pytest.ini_options]
pythonpath = [
  ".", "src",
]

或者

[pytest]
pythonpath = . src

将同时添加applib模块到sys.path,因此

import app
import lib

原始答案(不建议在最新 pytest 版本中使用;仅适用于 pytest<7):使用 conftest

最不侵入性的解决方案是在 repo/ 目录下添加一个名为 conftest.py 的空文件:

$ touch repo/conftest.py

就这样,不需要编写定制代码来操纵sys.path,也不需要记住带上PYTHONPATH,或者将__init__.py放置到不应该存在的目录中(虽然使用Apteryx答案中建议的python -m pytest是一个很好的解决方案!)。

之后的项目目录:

repo
├── conftest.py
├── app.py
├── settings.py
├── models.py
└── tests
     └── test_app.py

解释

pytest在测试集合中查找conftest模块以收集自定义钩子和固定装置,并为了从中导入自定义对象,pytestconftest.py的父目录添加到sys.path(在这种情况下是repo目录)。

其他项目结构

如果您有其他项目结构,请将conftest.py放置在包根目录中(包含包但本身不是包的目录,因此不包含__init__.py),例如:

repo
├── conftest.py
├── spam
│   ├── __init__.py
│   ├── bacon.py
│   └── egg.py
├── eggs
│   ├── __init__.py
│   └── sausage.py
└── tests
     ├── test_bacon.py
     └── test_egg.py

src布局

尽管这种方法可以与 src 布局一起使用(将 conftest.py 放置在 src 目录中):

repo
├── src
│   ├── conftest.py
│   ├── spam
│   │   ├── __init__.py
│   │   ├── bacon.py
│   │   └── egg.py
│   └── eggs 
│       ├── __init__.py
│       └── sausage.py
└── tests
     ├── test_bacon.py
     └── test_egg.py

请注意,将src添加到PYTHONPATH会减弱src布局的含义和好处!这样做将导致测试代码来自存储库而不是安装的包。如果您需要这样做,也许您根本不需要src目录。

下一步该怎么办

当然,conftest模块不仅仅是帮助发现源代码的文件;它是pytest框架的所有项目特定增强和测试套件自定义的地方。pytest他们的文档中有很多关于conftest模块的信息;从conftest.py:本地每个目录的插件开始。
此外,SO上有一个关于conftest模块的优秀问题:在py.test中,conftest.py文件有什么用?

2
这不起作用,就是这么简单。尝试将文件放在根目录下,然后从测试中导入该文件。将PYTHONPATH设置为根目录可以正常工作,但这种方法完全没有帮助。 - aaa90210
2
@aaa90210,虽然我无法重现您的问题(从根目录中的conftest导入在任何级别上都可以正常工作),但您不应该从conftest文件中导入,因为pytest将其视为保留名称,并强烈建议不要这样做。通过这样做,您会为未来的错误埋下伏笔。请创建另一个名为“utils.py”的模块,并在其中放置用于测试复用的代码。 - hoefling
10
从逻辑上讲,conftest.py 文件不属于应用程序代码,并且我认为将其放在 src/ 目录下是不正确的。 - Nik O'Lai
使用正确的文件名而不是foo和bar,额外奖励一轮奶酪! - ruslaniv
1
这对我来说非常有效。希望pytest能够识别我拥有的.pth文件。 - undefined
显示剩余8条评论

153

我遇到了同样的问题。通过在我的tests目录中添加一个空的__init__.py文件来解决这个问题。


106
注意,这 是 py.test 推荐的做法:避免在测试目录中使用“__init__.py”文件。这样,您的测试可以轻松地针对已安装版本的 mypkg 运行,而与已安装的包是否包含测试无关。来源:http://pytest.org/latest/goodpractises.html - K.-Michael Aye
40
我也有同样的问题,后来我发现将我的测试目录中的__init__.py文件删除解决了这个问题。 - 101
7
@mafro 我不明白问题在哪里?测试用例不需要是可导入的代码,它们可以被测试运行器找到。只有待测试的代码应该是一个已安装的包/模块,而不是测试用例。 - K.-Michael Aye
4
测试代码不应使用相对导入。在导入样式方面,您应将测试代码视为不属于包的一部分,而仅通过已安装的包名称导入所有内容。但即使如此,我仍然看不出将测试代码作为包来解决这个问题的理论上的可行性。 - K.-Michael Aye
6
test/ 子目录中添加一个 __init__.py 文件可以使得针对被安装的模块在该子目录下运行特定测试时绝对导入起作用。谢谢。 - Bryce Guinta
显示剩余5条评论

132

是的,如果你切换到测试目录并使用cd命令,则源文件夹不在Python的路径中。

你有两个选择:

  1. 手动将路径添加到测试文件中。类似于这样:

  2.  import sys, os
     myPath = os.path.dirname(os.path.abspath(__file__))
     sys.path.insert(0, myPath + '/../')
    
  3. 使用环境变量 PYTHONPATH=../ 运行测试。


16
我什么时候会使用cd进入一个目录?我正在从我的根目录运行py.test。除非我误解了你的意思,你是指当pytest遍历我的文件夹时。 - MattoTodd
1
如果这是一个 cd 的问题,那我在 Mac 上也会遇到同样的问题吗? - MattoTodd
4
是的,但我的目录结构通常有些不同 - 我通常将/src和/test保留在根目录下。 - Not_a_Golfer
有没有最佳实践来使用修改后的pythonpath来准备测试?我想只需编写“py.test”即可使用某些配置文件(我在一些项目根目录中看到了pytest.py文件)。 - atripes
我通过更改创建venv目录的位置来“修复”它。将我的venv/目录创建在tests/旁边(而不仅仅是在存储库的根目录),使pytest能够看到它,而无需修改路径。 - Everett
显示剩余6条评论

64

使用以下命令将pytest本身作为模块运行:python -m pytest tests

当项目层次结构为package/src package/tests时,在测试中从src导入时会发生这种情况。作为模块执行将把导入视为绝对路径而不是相对于执行位置的路径。


1
这个解决方案帮了我很大的忙,谢谢!问题的原因是Python版本冲突。pytest测试适用于早期的Python版本。在我的情况下,我的Python版本是3.7.1,python -m pytest tests可以工作,但pytest tests不行。 - Ruxi Zhang
6
使用命令 "python -m pytest [...]" 而不是 "pytest [...]" 可以实现几乎相同的行为,除此之外,前者会将当前目录添加到 sys.path。 - Moad Ennagi
这是目前最清晰的解决方案。 - Debajit

41

你可以在项目根目录下使用PYTHONPATH运行

PYTHONPATH=. py.test

或者使用pip install作为可编辑导入

pip install -e .   # install package using setup.py in editable mode

3
在包含test和src目录的目录中调用,而且test目录不在src目录结构中,这对我没有用。 - Zelphir Kaltstahl

29

在Flask中我遇到了同样的问题。

当我添加以下内容时:

__init__.py

将文件移动到tests文件夹后,问题消失了 :)

可能应用程序无法将tests文件夹识别为一个模块。


24

我创建了这个作为对你的问题和我的困惑的回答。希望它能帮到你。请注意py.test命令行和tox.ini中的PYTHONPATH。

https://github.com/jeffmacdonald/pytest_test

具体来说:您必须告诉py.test和tox在哪里找到您包含的模块。

使用py.test,您可以这样做:

PYTHONPATH=. py.test

使用tox,将以下内容添加到您的tox.ini文件中:

[testenv]
deps= -r{toxinidir}/requirements.txt
commands=py.test
setenv =
    PYTHONPATH = {toxinidir}

链接已失效:“404。这不是您要查找的页面。” - Peter Mortensen
你在说什么“tox”?这是一个什么项目对应的GitHub位置),具体可以参考这个链接 - Peter Mortensen

17

我通过删除源代码所在父文件夹中的顶级__init__.py文件来解决了这个问题。


5
修好了。有人能解释一下吗? - aboger
1
这个对我也解决了问题。如果有人能解释一下,我肯定会很感激的。 - king_wayne
我添加了__init__.py文件,但仍然遇到相同的问题,但这个解决方案对我也起作用了。原因是什么? - Abhijit
1
将其笨拙地移除后,它也对我起作用了... - AlexGeorg
这是否超出了“摸着石头过河”(试错)的范畴?背后的理论是什么?它是否可靠?其后果是什么? - Peter Mortensen

10

当我不小心向我的 src 目录中添加了 __init__.py 文件(它本不应该是 Python 包,而只是所有源代码的容器)时,我开始出现奇怪的 ConftestImportFailure: ImportError('No module named ... 错误。


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