【问题标题】:Avoid package name collision in Python Unittest避免 Python Unittest 中的包名冲突
【发布时间】:2019-03-04 20:55:23
【问题描述】:

这是我的项目布局:

project
+-- package_1
|   +-- __init__.py
|   +-- module_1.py tests
+-- package_2
|   +-- __init__.py
|   +-- module_2.py tests
+-- tests
    +-- package_1
    |   +-- __init__.py
    |   +-- test_module_1.py
    +-- package_2
        +-- __init__.py
        +-- test_module_2.py

test_module_1.py 开始于:

import package_1.module_1

test_module_2.py 开始于:

import package_2.module_2

从项目目录运行python -m unittest discover tests 会报错:

EE
======================================================================
ERROR: package_1.test_module_1 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: package_1.test_module_1
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 434, in _find_test_path
    module = self._get_module_from_name(name)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 375, in _get_module_from_name
    __import__(name)
  File "/Users/maggyero/project/tests/package_1/test_module_1.py", line 1, in <module>
    import package_1.module_1
ModuleNotFoundError: No module named 'package_1.module_1'


======================================================================
ERROR: package_2.test_module_2 (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: package_2.test_module_2
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 434, in _find_test_path
    module = self._get_module_from_name(name)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 375, in _get_module_from_name
    __import__(name)
  File "/Users/maggyero/project/tests/package_2/test_module_2.py", line 1, in <module>
    import package_2.module_2
ModuleNotFoundError: No module named 'package_2.module_2'


----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (errors=2)

在test_module_1.py开头添加import sys; print(sys.modules['package_1'])和在test_module_2.py开头添加import sys; print(sys.modules['package_2']),看看sys.modules里面有什么cache表示tests目录下的package_1和package_2已经导入在测试发现期间:

<module 'package_1' from '/Users/maggyero/project/tests/package_1/__init__.py'>
<module 'package_2' from '/Users/maggyero/project/tests/package_2/__init__.py'>

导入以前导入的包会重用来自sys.modules 的相同缓存包,即使sys.path 已经更新。因此,当执行import package_1.module_1import package_2.module_2 时,首先重新导入测试目录(包含test_module_1 和test_module_2)中的package_1 和package_2,而不是项目目录(包含module_1 和module_2)中的package_1 和package_2,然后重新导入module_1 和module_2被导入,引发ModuleNotFoundError

除了重命名之外,是否有避免测试目录中的包影响项目目录的包的解决方法?

更新(发布答案)

以下 Laurent Laporte 的替代解决方案(他避免在执行 sys.modules 时已经在 sys.modules 中使用 import package_1.module_1import package_2.module_2,而是使用 'tests.package_1''tests.package_2',这要归功于顶级目录的变化)是更新sys.path并重新加载test_module_1.py中的包:

import importlib
import pathlib
import sys
sys.path.insert(0, pathlib.Path(__file__).resolve().parents[2])

import package_1
importlib.reload(package_1)
import package_1.module_1

和 test_module_2.py:

import importlib
import pathlib
import sys
sys.path.insert(0, pathlib.Path(__file__).resolve().parents[2])

import package_2
importlib.reload(package_2)
import package_2.module_2

此解决方案的唯一优点是测试目录不需要是常规包(即具有 __init__.py 文件)。因此,当 Unittest 允许递归命名空间包发现时不会有任何优势(目前票证仍处于打开状态:https://bugs.python.org/issue23882)。

应该首选Laurent Laporte 的解决方案,因为包限定包重新加载 更能区分同名包。另一个好的解决方案是包重命名(例如,将包_1 和包_2 从测试目录重命名为 test_package_1 和 test_package_2)。

【问题讨论】:

  • package1 重命名为test_package1

标签: python unit-testing import python-import python-unittest


【解决方案1】:

您可以通过使用标志 -t--top-level-directory directory 设置顶级目录来解决您的问题您的项目(默认为启动目录)

例如:

python -m unittest discover tests -t .

但是,为了discover 导入您的测试模块,您需要通过在其中插入__init__.py 来将您的tests 目录转换为一个包。

请参阅有关 tests discovery 的文档。

注意事项:

  • 我在使用 PyTest 时遇到了同样的问题。在 GitHub 上查看我的 study

  • 在其他开源项目中,只有一个根包(例如只有package_1),并且没有tests/package_1目录,只有tests和所有test_*.py模块(可能带有子包)。所以,问题没有出现。

【讨论】:

  • 与 Bakuriu 建议的将 package_1 和 package_2 重命名为 test_package_1 和 test_package_1 相比,最佳实践是什么?
  • 其实你不需要重命名你的测试包。很多开源项目都没有。最终你可以创建一个src 目录来放置你的主包appart 从tests 目录。
  • 我不喜欢必须将测试目录变成一个包的事实。我想知道为什么发现不搜索没有 __init__.py 文件的目录(也就是说命名空间包),知道吗?最后将 package_1 重命名为 test_package_1 是有意义的,因为 module_1 在测试中已经被称为 test_module_1。这种方式更加一致,而且不需要向 unittest 添加命令行参数。但我接受了你的回答,因为它完美地回答了限制范围内的问题。谢谢!
  • 这张开放票(自 2015 年起)将使 unittest 发现递归地找到 namespace 包:bugs.python.org/issue23882。这样,从项目目录运行python -m unittest(或等效的python -m unittest discover,不带--start-directory--top-level-directory 参数)将找到所有测试,而无需创建__init__.py 文件一直到测试转命名空间包到常规包中。也许您可以最终确定提议的补丁以关闭问题,以便我们在下一个 Python 版本中拥有此功能。
  • 我找到了您的替代解决方案(请参阅上面的更新),即使您的解决方案应该是首选。顺便说一句,在您的解决方案中更改顶级目录就足够了,开始目录甚至不需要是测试,它可以与顶级目录相同。重要的一点是在tests目录中添加一个__init__.py文件,把它变成一个常规的包,用于包发现。所以从项目目录运行python -m unittest,或者等效的python -m unittest discover,解决了包名冲突问题。
猜你喜欢
  • 2012-07-21
  • 2016-03-27
  • 2011-07-02
  • 2014-03-12
  • 2015-03-21
  • 1970-01-01
  • 1970-01-01
  • 2011-12-31
  • 1970-01-01
相关资源
最近更新 更多