在 Python 单元测试中避免包名冲突

Avoid package name collision in Python Unittest

这是我的项目布局:

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.

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

更新(post 回答)

Laurent Laporte 下面的替代解决方案(他在执行 import package_1.module_1import package_2.module_2 时避免了 'package_1''package_2' 已经在 sys.modules 中,方法是'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的解决方案应该是首选,因为包限定包重新加载更能区分同名包。另一个好的解决方案是 package 重命名(例如将 package_1 和 package_2 从测试目录重命名为 test_package_1 和 test_package_2) .

你可以使用标志 -t, --top-level-directory directory 来解决你的问题 设置项目的顶层目录(默认为开始目录)

例如:

python -m unittest discover tests -t .

但是,为了让 discover 导入您的测试模块,您需要通过在其中插入 __init__.pytests 目录变成一个包.

请参阅有关 tests discovery 的文档。

备注:

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

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