关于python:使用pytest正确导入

Importing correctly with pytest

我刚准备好将pytest与python 2.6结合使用。到目前为止,它工作得很好,除了处理"import"语句:我似乎无法让pytest像我的程序那样响应导入。

我的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
src/
    main.py
    util.py
    test/
        test_util.py
    geom/
        vector.py
        region.py
        test/
            test_vector.py
            test_region.py

要运行,我从src/调用python main.py

在main.py中,我导入向量和区域

1
2
from geom.region import Region
from geom.vector import Vector

在vector.py中,我导入带有

1
from geom.region import Region

当我在标准运行中运行代码时,这些都可以正常工作。但是,当我从src/调用"py.test"时,它始终会退出并出现导入错误。

一些问题和我的解决方案尝试

我的第一个问题是,当运行"test/test foo.py"时,py.test无法直接"导入foo.py"。我用"imp"工具解决了这个问题。在"test-tuil.py"中:

1
2
import imp
util = imp.load_source("util","util.py")

这对许多文件都很有用。这似乎也意味着,当py test运行"path/test/test_foo.py"to test"path/foo.py"时,它基于"path"目录。

但是,"test_vector.py"失败。pytest可以找到并导入vector模块,但它找不到vector的任何导入。使用pytest时,以下导入(来自"vector.py")均失败:

1
2
from geom.region import *
from region import *

这两个都给出了错误的形式

1
ImportError: No module named [geom.region / region]

我不知道下一步该怎么做来解决这个问题;我对Python中的导入的理解是有限的。

在使用pytest时,处理导入的正确方法是什么?

编辑:非常简单的解决方案

vector.py中,我将进口报表从

1
from geom.region import Region

简单地

1
from region import Region

这使得导入相对于"vector.py"目录。

接下来,在"test/test_vector.py"中,我将"vector.py"目录添加到路径中,如下所示:

1
2
import sys, os
sys.path.append(os.path.realpath(os.path.dirname(__file__)+"/.."))

这使python能够从"geom/test/test_vector.py"中找到"./region.py"。

这是可行的,但它似乎是非常有问题的,因为我正在向路径中添加大量的新目录。我要找的是

1)与Pytest兼容的导入策略,或

2)Pytest中的一个选项,使其与我的导入策略兼容

所以我将这个问题留给这些答案。


import在以下目录中查找模块:

  • 程序的主目录。这是根脚本的目录。当您运行pytest时,您的主目录就是安装它的地方(可能是/usr/local/bin)。无论您是从SRC目录运行它,因为pytest的位置决定了您的主目录。这就是它找不到模块的原因。
  • Python PATH。这是一个环境变量。您可以从操作系统的命令行设置它。在Linux/Unix系统中,可以通过执行"export python path=/your/custom/path"来实现这一点。如果希望python从测试目录中查找模块,则应在该变量中包含src路径。
  • 标准库目录。这是安装所有库的目录。
  • 使用pth文件有一个不太常见的选项。
  • sys.path是主目录、pythonpath和标准库目录组合的结果。您所做的,修改sys.path是正确的。这是我经常做的事。如果你不喜欢弄乱sys.path,可以尝试使用pythonpath。


    这里的问题是,pytest在文件系统中查找包含测试的文件,但随后需要生成一个模块名,该模块名将导致import加载该文件。(记住,文件不是模块。)

    pytest通过查找不包含__init__.py文件的文件级别或更高级别的第一个目录,并声明包含从该文件生成的模块的模块树的"basedir",得出了该测试包的名称。然后,它将basedir添加到sys.path中,并使用模块名导入,该模块名将查找与basedir相关的文件。

    你应该注意这其中的一些含义:

  • 基本路径可能与预期的基本路径不匹配,在这种情况下,模块的名称将与您通常使用的名称不匹配。例如,您所认为的geom.test.test_vector实际上将在pytest运行期间命名为test_vector,因为它在src/geom/test/中没有发现__init__.py,因此将该目录添加到sys.path

  • 如果不同目录中的两个文件同名,则可能会发生模块命名冲突。例如,在任何地方缺少__init__.py文件,添加geom/test/test_util.py将与test/test_util.py冲突,因为两者都作为import test_util.py加载,路径中同时加载test/geom/test/

  • 您在这里使用的系统没有显式的__init__.py模块,而是让python为您的目录创建隐式名称空间包。(包是带有子模块的模块。)理想情况下,我们将Pytest配置为一个路径,从中它也可以生成这个路径,但它似乎不知道如何做。

    这里最简单的解决方案是将空的__init__.py文件添加到src/下的所有子目录;这将导致pytest使用以src/下目录名开头的包/模块名导入所有内容。

    问题是如何使用PEP420名称空间包对项目进行pytest?讨论其他解决方案。


    我也在想怎么解决这个问题。在阅读了这篇文章,并四处游玩之后,我想出了一个优雅的解决方案。我创建了一个名为"test_setup.py"的文件,并在其中输入以下代码:

    1
    2
    import sys, os
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))

    我把这个文件放在顶级目录(比如src)中。当从顶层目录运行pytest时,它将运行所有测试文件,包括这个文件,因为该文件的前缀是"test"。文件中没有测试,但它仍然在运行,因为它以"test"开头。

    代码将把test_setup.py文件的当前目录名附加到测试环境中的系统路径。这将只做一次,所以没有一堆东西添加到路径中。

    然后,在任何测试函数中,您都可以导入与顶层文件夹相关的模块(如import geom.region),并且它知道在哪里找到它,因为src目录被添加到路径中。

    如果要运行单个测试文件(如test_util.py)而不是所有文件,则应使用:

    1
    pytest test_setup.py test\test_util.py

    这将运行测试设置和测试实用程序代码,以便仍然可以使用测试设置代码。