我正在用Python编写一个包。我使用virtualenv。我在virtualenv的.pth路径中设置了模块的根路径,这样我就可以在开发代码和测试时导入包的模块(问题1:这是一种好方法吗?)这很好(这是一个例子,这是我想要的行为):

(VEnvTestRc) zz@zz:~/Desktop/GitFolders/rc$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from rc import ns
>>> exit()
(VEnvTestRc) zz@zz:~/Desktop/GitFolders/rc$ python tests/test_ns.py 
issued command: echo hello
command output: hello

但是,如果我尝试使用PyTest,我会得到一些导入错误消息:

(VEnvTestRc) zz@zz:~/Desktop/GitFolders/rc$ pytest
=========================================== test session starts ============================================
platform linux2 -- Python 2.7.12, pytest-3.0.5, py-1.4.31, pluggy-0.4.0
rootdir: /home/zz/Desktop/GitFolders/rc, inifile: 
collected 0 items / 1 errors 

================================================== ERRORS ==================================================
________________________________ ERROR collecting tests/test_ns.py ________________________________
ImportError while importing test module '/home/zz/Desktop/GitFolders/rc/tests/test_ns.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_ns.py:2: in <module>
    from rc import ns
E   ImportError: cannot import name ns
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================= 1 error in 0.09 seconds ==========================================
(VEnvTestRc) zz@zz:~/Desktop/GitFolders/rc$ which pytest
/home/zz/Desktop/VirtualEnvs/VEnvTestRc/bin/pytest

我有点困惑,这似乎表明一个导入错误,但Python做得很好,为什么有一个问题,特别是与PyTest?对原因/补救(问题2)有什么建议吗?我在谷歌上搜索了PyTest的“ImportError: cannot import”错误,但我得到的点击率与丢失的python路径和补救措施有关,这似乎不是这里的问题。有什么建议吗?


当前回答

另一个建议

我在SO和其他地方探讨了这个问题和其他各种问题……所有关于在项目目录结构的各个部分添加(或删除)一个空__init__.py和/或conftest.py的东西,所有关于PYTHONPATH的东西,等等:这些解决方案都不适合我,在实际上是一个非常简单的情况下,这不应该造成任何痛苦。

I regard this as a flaw in pytest's current setup. In fact I got a message recently from someone on SO who clearly knew his stuff. He said that pytest is not designed to work with (as per Java/Groovy/Gradle) separate "src" and "test" directory structures, and that test files should be mingled in amongst the application directories and files. This perhaps goes some way to providing an explanation ... however, tests, particularly integration/functional tests, don't always necessarily correspond neatly to particular directories, and I think pytest should give users more choice in this regard.

项目结构:

project_root
    src
        core
            __init__.py
            __main__.py
            my_other_file.py 
    tests
        basic_tests
            test_first_tests.py

导入问题提出:非常简单,__main__.py有一行import my_other_file。当我只是运行应用程序时,这(并不奇怪)工作正常,即从根目录运行python src/core。

但是当我用导入__main__.py的测试运行pytest时,我得到

ModuleNotFoundError: No module named 'my_other_file'

在__main__.py中试图导入“my_other_file”的行中。请注意,在我的例子中,这里的问题是,在pytest测试过程中,一个应用程序文件无法在同一个包中找到另一个应用程序文件。

使用PYTHONPATH环境 经过大量的实验,并将__init__.py文件和confest.py文件放在我能找到的几乎每个目录中(我认为关键文件是__init__.py添加到“tests”和“basic_tests”中,参见上面的目录结构),然后将PYTHONPATH设置为如下所示

PYTHONPATH=D:\My Documents\software projects\EclipseWorkspace\my_project\src\core

... 我发现它很有效。在测试文件中的导入必须进行一些调整,一般模式是从核心导入app, project,但测试文件能够“看到”核心,关键是没有必要在应用文件中的导入命令中添乱。

然而……由于某种原因,使用此方法的测试现在运行得慢得多!下面的解决方案只加载了一次核心包,与之相比,我怀疑PYTHONPATH解决方案可能会导致大量代码被一次又一次地重新加载。我还不能证实这一点,但我看不出任何其他解释。

另一种选择 我的另一个相当简单的解决方案是:

1 -在应用程序包的__init__.py中(即“core”目录),放入以下两行:

import pathlib, sys
sys.path.append(str(pathlib.Path(__file__).parent))

注意,通常__init__.py中没有任何东西。事实证明,正如我通过实验证实的那样,pytest通常(参见下面的更新)在这种情况下执行__init__.py,在pytest完成了它所做的任何事情之后。路径条目。

2-更新2022-01: 我发现的原始解决方案涉及在应用程序目录(ies)中放置一个conftest.py文件-没有它就不能工作。这显然是不可取的。我发现另一个解决方案是把这段代码放在你根目录下的conftest.py文件中:

def pytest_configure(config):
    import src.core # NB this causes `src/core/__init__.py` to run
    # set up any "aliases" (optional...)
    import sys
    sys.modules['core'] = sys.modules['src.core']

... 实际上,从我的实验来看,将conftest.py放在应用程序目录中的效果似乎是pytest随后在该目录中运行__init__.py。这似乎暗示模块正在被导入…

(以前的建议:) 是的,你还必须在“core”目录下包含一个空的“conftest.py”文件。希望这应该是你唯一需要的conftest.py:我对所有这些都进行了试验,将一个放在根目录中是不必要的(如果没有__init__.py中建议的代码,它也不能解决问题)。

3 -最后,在我的测试函数,之前调用核心。__main__在我的例子中,我必须导入我知道即将被导入的文件:

import core.my_other_file
import core.__main__ 

如果您在文件中的第一个测试中执行此操作,您将发现sys。为该文件中的所有其他测试设置了Modules。更好的是,输入import core。My_other_file在文件的最开始,在第一个测试之前。不幸的是,from core import *似乎不起作用。

后来:这种方法有一些特点和局限性。例如,虽然-k开关可以过滤入/出测试或整个文件,但如果您执行类似pytest tests/tests_concerning_module_x之类的操作,似乎core.__init__.py没有得到运行…因此,在测试期间,核心模块中的文件再次相互不可导入。其他限制可能会被揭示出来。


正如我所说,我认为这是pytest设置中的一个缺陷。我完全不知道pytest做了什么来为sys. net建立一个常识性的设置。路径,但它显然是错的。不应该依赖PYTHONPATH或其他方法,如果这确实是“官方”解决方案,那么关于这个主题的文档非常缺乏。

注意我的这个建议有一个问题:通过添加到sys。当pytest每次在一个模块中运行__init__.py时,它意味着这个新路径此后在sys. py中成为永久路径。路径,在测试期间,更令人担忧的是,在应用程序本身的运行期间,如果有任何东西实际上调用__init__.py。(顺便说一句,只是使用python src/core(就像我的例子中那样)不会导致这种情况发生。但其他事情可能会。)

为了解决这个问题,我有一个笨拙但有效的解决方案:

import pathlib, sys, traceback
frame_list = traceback.extract_stack()
if len(frame_list) > 2:
    path_parts = pathlib.Path(frame_list[2].filename).parts
    if len(path_parts) > 2:
        module_dir_path_str = str(pathlib.Path(__file__).parent)
        if sys.platform.startswith('win'):
            if path_parts[-3:-1] == ('Scripts', 'pytest.exe'):
                sys.testing_context = True
                sys.path.append(module_dir_path_str)
        elif sys.platform.startswith('lin'):
            if path_parts[-3:-1] == ('_pytest', 'config'):
                sys.testing_context = True
                sys.path.append(module_dir_path_str)

这是基于我对pytest在W10上下文中和Linux Mint 20上下文中运行某些东西时的堆栈跟踪的检查。这意味着在应用程序运行时,不会混淆sys.path。

当然,这可能会与未来的pytest版本有所不同。我的版本是6.2.5。

其他回答

可能是Pytest没有将包作为Python模块读取,而Python是(可能是由于路径问题)。尝试更改pytest脚本的目录或显式地将模块添加到PYTHONPATH中。

也可能是您的机器上安装了两个版本的Python。检查Python源代码中的pytest和您运行的Python shell。如果它们是不同的(即Python 2 vs 3),使用source activate来确保您运行的pytest安装在安装模块的同一个Python中。

在我的例子中,我在一个容器中工作,不幸的是,pytest倾向于使用python2.7而不是我选择的python3解释器。

在我的情况下,这是有效的:

python3 -m pytest

我的文件夹结构

/
app/
-module1.py
-module2.py
-tests/
--test_module1.py
--test_module2.py
requirements.txt
README.md

另一个建议

我在SO和其他地方探讨了这个问题和其他各种问题……所有关于在项目目录结构的各个部分添加(或删除)一个空__init__.py和/或conftest.py的东西,所有关于PYTHONPATH的东西,等等:这些解决方案都不适合我,在实际上是一个非常简单的情况下,这不应该造成任何痛苦。

I regard this as a flaw in pytest's current setup. In fact I got a message recently from someone on SO who clearly knew his stuff. He said that pytest is not designed to work with (as per Java/Groovy/Gradle) separate "src" and "test" directory structures, and that test files should be mingled in amongst the application directories and files. This perhaps goes some way to providing an explanation ... however, tests, particularly integration/functional tests, don't always necessarily correspond neatly to particular directories, and I think pytest should give users more choice in this regard.

项目结构:

project_root
    src
        core
            __init__.py
            __main__.py
            my_other_file.py 
    tests
        basic_tests
            test_first_tests.py

导入问题提出:非常简单,__main__.py有一行import my_other_file。当我只是运行应用程序时,这(并不奇怪)工作正常,即从根目录运行python src/core。

但是当我用导入__main__.py的测试运行pytest时,我得到

ModuleNotFoundError: No module named 'my_other_file'

在__main__.py中试图导入“my_other_file”的行中。请注意,在我的例子中,这里的问题是,在pytest测试过程中,一个应用程序文件无法在同一个包中找到另一个应用程序文件。

使用PYTHONPATH环境 经过大量的实验,并将__init__.py文件和confest.py文件放在我能找到的几乎每个目录中(我认为关键文件是__init__.py添加到“tests”和“basic_tests”中,参见上面的目录结构),然后将PYTHONPATH设置为如下所示

PYTHONPATH=D:\My Documents\software projects\EclipseWorkspace\my_project\src\core

... 我发现它很有效。在测试文件中的导入必须进行一些调整,一般模式是从核心导入app, project,但测试文件能够“看到”核心,关键是没有必要在应用文件中的导入命令中添乱。

然而……由于某种原因,使用此方法的测试现在运行得慢得多!下面的解决方案只加载了一次核心包,与之相比,我怀疑PYTHONPATH解决方案可能会导致大量代码被一次又一次地重新加载。我还不能证实这一点,但我看不出任何其他解释。

另一种选择 我的另一个相当简单的解决方案是:

1 -在应用程序包的__init__.py中(即“core”目录),放入以下两行:

import pathlib, sys
sys.path.append(str(pathlib.Path(__file__).parent))

注意,通常__init__.py中没有任何东西。事实证明,正如我通过实验证实的那样,pytest通常(参见下面的更新)在这种情况下执行__init__.py,在pytest完成了它所做的任何事情之后。路径条目。

2-更新2022-01: 我发现的原始解决方案涉及在应用程序目录(ies)中放置一个conftest.py文件-没有它就不能工作。这显然是不可取的。我发现另一个解决方案是把这段代码放在你根目录下的conftest.py文件中:

def pytest_configure(config):
    import src.core # NB this causes `src/core/__init__.py` to run
    # set up any "aliases" (optional...)
    import sys
    sys.modules['core'] = sys.modules['src.core']

... 实际上,从我的实验来看,将conftest.py放在应用程序目录中的效果似乎是pytest随后在该目录中运行__init__.py。这似乎暗示模块正在被导入…

(以前的建议:) 是的,你还必须在“core”目录下包含一个空的“conftest.py”文件。希望这应该是你唯一需要的conftest.py:我对所有这些都进行了试验,将一个放在根目录中是不必要的(如果没有__init__.py中建议的代码,它也不能解决问题)。

3 -最后,在我的测试函数,之前调用核心。__main__在我的例子中,我必须导入我知道即将被导入的文件:

import core.my_other_file
import core.__main__ 

如果您在文件中的第一个测试中执行此操作,您将发现sys。为该文件中的所有其他测试设置了Modules。更好的是,输入import core。My_other_file在文件的最开始,在第一个测试之前。不幸的是,from core import *似乎不起作用。

后来:这种方法有一些特点和局限性。例如,虽然-k开关可以过滤入/出测试或整个文件,但如果您执行类似pytest tests/tests_concerning_module_x之类的操作,似乎core.__init__.py没有得到运行…因此,在测试期间,核心模块中的文件再次相互不可导入。其他限制可能会被揭示出来。


正如我所说,我认为这是pytest设置中的一个缺陷。我完全不知道pytest做了什么来为sys. net建立一个常识性的设置。路径,但它显然是错的。不应该依赖PYTHONPATH或其他方法,如果这确实是“官方”解决方案,那么关于这个主题的文档非常缺乏。

注意我的这个建议有一个问题:通过添加到sys。当pytest每次在一个模块中运行__init__.py时,它意味着这个新路径此后在sys. py中成为永久路径。路径,在测试期间,更令人担忧的是,在应用程序本身的运行期间,如果有任何东西实际上调用__init__.py。(顺便说一句,只是使用python src/core(就像我的例子中那样)不会导致这种情况发生。但其他事情可能会。)

为了解决这个问题,我有一个笨拙但有效的解决方案:

import pathlib, sys, traceback
frame_list = traceback.extract_stack()
if len(frame_list) > 2:
    path_parts = pathlib.Path(frame_list[2].filename).parts
    if len(path_parts) > 2:
        module_dir_path_str = str(pathlib.Path(__file__).parent)
        if sys.platform.startswith('win'):
            if path_parts[-3:-1] == ('Scripts', 'pytest.exe'):
                sys.testing_context = True
                sys.path.append(module_dir_path_str)
        elif sys.platform.startswith('lin'):
            if path_parts[-3:-1] == ('_pytest', 'config'):
                sys.testing_context = True
                sys.path.append(module_dir_path_str)

这是基于我对pytest在W10上下文中和Linux Mint 20上下文中运行某些东西时的堆栈跟踪的检查。这意味着在应用程序运行时,不会混淆sys.path。

当然,这可能会与未来的pytest版本有所不同。我的版本是6.2.5。

如果它与最初在python 2.7中开发,现在迁移到python 3的python代码有关。X认为这个问题可能与进口问题有关。

如。 当从位于同一目录的file: base导入一个对象时,这在python 2.x中是有效的:

from base import MyClass

在python中3。X你应该替换为基本全路径或。base 不这样做会导致上述问题。 所以尝试:

from .base import MyClass

Python导入系统的又一次巨大胜利。我认为没有达成共识的原因是,什么可行可能取决于您的环境和您在其之上使用的工具。

我从VS Code中使用这个,在conda环境下的Windows测试资源管理器中,Python 3.8。

我要工作的设置是:

mypkg/
    __init__.py
    app.py
    view.py
tests/
    test_app.py
    test_view.py

在这种设置下,智能感知和测试发现都起作用。

请注意,我最初尝试了以下建议。

src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    test_app.py
    test_view.py

我找不到从VS Code得到这个工作的方法,因为src文件夹只是吹了导入系统的头脑。我可以想象,有一种方法可以让它从命令行工作。作为一个相对较新的Python编程转换者,它给了我一种使用COM的怀旧感觉,但没有那么有趣。