我可以使用 python 模块的 main 进行测试吗?

Posted

技术标签:

【中文标题】我可以使用 python 模块的 main 进行测试吗?【英文标题】:Can I use a python module's main for testing? 【发布时间】:2020-07-29 22:14:34 【问题描述】:

我正在 Python 3.8.2 上开发一个 Python 库,我想将一个模块作为主模块运行以进行测试。当我尝试时,我收到 ModuleNotFound 错误。

这是我的库结构:

.
├── foo
│   ├── __init__.py
│   ├── bar.py
│   └── quux
│       ├── __init__.py
│       ├── corge.py
│       └── garply.py
├── main.py

bary.py:

def baz():
    print("baz")

corge.py

from foo.quux.garply import *


def grault():
    waldo()
    print("grault")


if __name__ == '__main__':
    grault()

garply.py

def waldo():
    print("waldo")

main.py

from foo.bar import *
from foo.quux.corge import *

if __name__ == '__main__':
    baz()
    grault()

(所有__init__.py文件都是空的)

当我运行main.py 时,它可以工作。

$ python main.py
baz
waldo
grault

如果我尝试运行corge.py,我会收到以下错误:

$ python foo/quux/corge.py 
Traceback (most recent call last):
  File "foo/quux/corge.py", line 1, in <module>
    from foo.quux.garply import *
ModuleNotFoundError: No module named 'foo'

不管我当前的工作目录是什么,它总是给出这个错误。

$ cd foo/quux/
$ python corge.py 
Traceback (most recent call last):
  File "corge.py", line 1, in <module>
    from foo.quux.garply import *
ModuleNotFoundError: No module named 'foo'

在测试这个时,我使用 PyCharm 2020.1 创建了一个新的 PyCharm 项目,并实现了我描述的结构。令我惊讶的是,它适用于默认检测到的运行配置。

我尝试使用 PyCharm 自动创建的 venv,但仍然无法正常工作。如果我直接复制/粘贴命令并使用他们的 CWD,它将不起作用。它不适用于 PyCharm 中内置的终端。它适用于 PyCharm 运行按钮。

我的模块结构有问题吗?如果是这样,PyCharm 可以做些什么来完成这项工作?如果不是,为什么它不能在 PyCharm 之外工作?

【问题讨论】:

这能回答你的问题吗? Python ModuleNotFoundError Testsuite (Import Error) ***.com/search?q=python+modulenotfounderror 【参考方案1】:

Python 需要知道foo 所在的目录才能导入它。 sys.path 列出了 python 将搜索的目录。

当你安装一个包时,安装程​​序会担心这样做——通常是将模块放在一个众所周知的目录中或将安装包路径添加到sys.path

当你运行一个脚本时,python会自动将该脚本的路径添加到sys.path,所以当你运行main.py时,你会找到foo

如何作为__main__的模块运行

一种选择是使软件包可安装(setup.py、wheels 等)并使用开发模式("pip install --editable ./" vs "python setup.py develop" 进行一些讨论)。这就是我在开发计划提供给其他人的东西时所做的事情。

另一种方法是将您的目录添加到 PYTHONPATH,甚至可能在您运行程序时添加。在 linux 上会是

PYTHONPATH=path/to/fooproject:$PYTHONPATH python foo/quux/corge.py

还有一个,我也这样做,是破解模块本身的路径。 __file__ 给出了相对于当前工作目录的文件名,并且您知道自己在包层次结构中的深度。因此,您可以将__file__ 设为绝对并剥离几个目录名称

corge.py

import sys
import os

if __name__ == "__main__":
    # I'm two levels deep in the package so package directory is
    packagedir = os.path.abspath(os.path.join(os.path.dirname(__file__),
        "..", ".."))
    sys.path.insert(0, packagedir)
    import foo

最后,不要一开始就这样做。当您将corge.py 作为脚本运行时,它会获得与作为模块导入的foo.bar.corge 不同的命名空间__main__。它的全局变量/类/函数被加载了两次,根据你是通过__main__命名空间还是foo.bar.corge调用它们,你会得到不同的变量。

最好将任何你想要放入corge.py 的主目录中的东西,将它们分开的脚本。例如,您可以将def main() 添加到您的模块中。在main.py 中,您可以添加一个选项--run foo.bar.corge,告诉main 导入corge.py 并运行它的main()argparse subcommands 可以用于此。

【讨论】:

这很有意义,谢谢!我最初的计划是使用它来运行单元测试,在模块中定义 test() 方法是否合适,或者应该以不同的方式处理? 是的,您可以将测试与代码放在一起 - 我不确定它的常见做法,但如果您这样做,它不会引起人们的注意。但是您将代码放入在正常运行时不需要存在的模块中。我倾向于使用nose 或只是 python 附带的 vanilla unittest(pytest 是另一种选择),我将它们分开到一个单独的目录中。我认为你应该为你的测试框架做任何最正常的事情。【参考方案2】:

您对main.py 的调用有效,因为Python 将在直接子目录中查找模块。想要导入foo.yadda.whatever 的任何其他位置的任何模块都必须通过搜索PYTHONPATH 来找到foo。因此,您需要将foo 的父目录添加到您的PYTHONPATH

【讨论】:

以上是关于我可以使用 python 模块的 main 进行测试吗?的主要内容,如果未能解决你的问题,请参考以下文章

重新导入被测模块以丢失上下文

Python使用plotly绘制数据图表的方法

在标准输入上使用相对导入的Python 3脚本给出错误:没有名为'__main__.XXX'的模块; '__main__'不是包

解决pip3的ImportError: cannot import name 'main'

Python - 从导入的模块注册函数

使用Python进行模拟时避免使用冗余@patch