分离应用程序和包测试时的导入问题
Import problem when separating applications and package tests
也许我的目标和我在这里尝试做的事情在 unpythonic 的意义上是错误的。我愿意就此提出任何建议。
我的目标
- 具有自己的测试文件夹的应用程序 (
myapp
)。
- 一个包(
mypackage
)有自己的测试文件夹。
- 包的测试应该运行能够从应用程序文件夹和包文件夹中进行。
- 该包具有隐式和显式组件。后者需要显式导入(例如通过
import mypackage.mymoduleB
)。
- 可以将包(文件夹)复制(发送以在其他应用程序中重用?)到其他文件系统位置,而不会失去其功能和可测试性。这就是为什么
tests
在包文件夹内而不是在包文件夹外的原因。
这是文件夹树,其中 itest
是项目名称,myapp
是其中包含 if __name__ == '__main__':
的应用程序,mypackag
是包。
itest
└── myapp
├── myapp.py
├── mypackage
│ ├── __init__.py
│ ├── _mymoduleA.py
│ ├── mymoduleB.py
│ └── tests
│ ├── __init__.py
│ └── test_all.py
└── tests
├── __init__.py
└── test_myapp.py
问题
我可以 运行 应用程序目录中的单元测试没有问题。
/home/user/tab-cloud/_transfer/itest/myapp $ python3 -m unittest -vvv
test_A (mypackage.tests.test_all.TestAll) ... mymoduleA.foo()
ok
test_B (mypackage.tests.test_all.TestAll) ... mymoduleB.bar()
ok
test_myname (tests.test_myapp.TestMyApp) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
但是当我进入包装内部时,测试没有 运行(即目标 #3)。
/home/user/tab-cloud/_transfer/itest/myapp/mypackage $ python3 -m unittest -vvv
tests.test_all (unittest.loader._FailedTest) ... ERROR
======================================================================
ERROR: tests.test_all (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_all
Traceback (most recent call last):
File "/usr/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/usr/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/home/user/tab-cloud/_transfer/itest/myapp/mypackage/tests/test_all.py", line 12, in <module>
from . import mypackage
ImportError: cannot import name 'mypackage' from 'tests' (/home/user/tab-cloud/_transfer/itest/myapp/mypackage/tests/__init__.py)
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
MWE
不,我给你看文件。为了确保在 运行 时使用正确的 import
从应用程序文件夹或我使用的包文件夹 importlib
(基于 foreign solution)对包进行测试。
三个文件组成包
这是myapp/mypackage/__init__.py
:
# imported implicite via 'mypackage'
from ._mymoduleA import *
# 'mymoduleB' need to be imported explicite
# via 'mypackage.moduleB'
这是myapp/mypackage/_mymoduleA.py
:
def foo():
print('mymoduleA.foo()')
return 1
这是myapp/mypackage/mymoduleB.py
:
def bar():
print('mymoduleB.bar()')
return 2
包的测试
myapp/mypackage/tests/__init__.py
为空。
这是myapp/mypackage/tests/test_all.py
:
import importlib
import unittest
# The package should be able to be tested by itself (run unittest inside the
# package directory) AND from the using application (run unittest in
# application directory).
# Based on:
if importlib.util.find_spec('mypackage'):
import mypackage
import mypackage.mymoduleB
else:
from . import mypackage
from mypackage import mymoduleB
class TestAll(unittest.TestCase):
def test_A(self):
self.assertEqual(1, mypackage.foo())
def test_B(self):
self.assertEqual(2, mypackage.mymoduleB.bar())
申请
这是cat myapp/myapp.py
:
#!/usr/bin/env python3
import mypackage
def myname():
return 'My application!'
if __name__ == '__main__':
print(myname())
mypackage.foo()
try:
mypackage.mymoduleB.bar()
except AttributeError:
# we expecting this
print('Not imported yet: "mymoduleB.bar()"')
# this should work
import mypackage.mymoduleB
mypackage.mymoduleB.bar()
应用程序测试
myapp/tests/__init__.py
为空。
这是myapp/tests/test_myapp.py
:
import unittest
import myapp
class TestMyApp(unittest.TestCase):
def test_myname(self):
self.assertEqual(myapp.myname(), 'My application!')
旁注
请让我解释一下我的目标。 mypackage
应该可以在其他项目中重复使用。实际上,这意味着我将 mypackage
文件夹从一个地方复制到另一个地方。在复制该文件夹时,我确实希望 tests
文件夹随附它而无需明确考虑它,因为它在包文件夹之外。如果新项目进行单元测试,则包的测试应该自动参与该单元测试(通过 discover
)。
几年前我创建了一个导入库。它适用于路径。我用它来创建一个插件系统,我基本上可以在其中安装和导入任何库的多个版本(有一些限制)。
为此,我们获取模块的当前路径。然后我们使用路径导入包。该库会自动将正确的路径添加到 sys.path.
您需要做的就是安装 pylibimp pip install pylibimp
并编辑 myapp/mypackage/tests/test_all.py
import os
import pylibimp
import unittest
path_tests = os.path.join(os.path.dirname(__file__))
path_mypackage = os.path.dirname(path_tests)
path_myapp = os.path.dirname(path_mypackage)
mypackage = pylibimp.import_module(os.path.join(path_myapp, 'mypackage'), reset_modules=False)
class TestAll(unittest.TestCase):
def test_A(self):
self.assertEqual(1, mypackage.foo())
def test_B(self):
self.assertEqual(2, mypackage.mymoduleB.bar())
我觉得背景比较简单
import os
import sys
sys.path.insert(0, os.path.abspath('path/to/myapp'))
# Since path is added we can "import mypackage"
mypackage = __import__('mypackage')
sys.path.pop(0) # remove the added path to not mess with other imports
希望这就是您要找的。
你的目标真的有点不符合Python。但有的时候,还是要打破常规才能放飞心灵。
您可以通过检查 myapp/mypackage/__init__.py
中的 __package__
属性来解决问题,如下所示:
# hint from there:
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
if __package__:
from ._mymoduleA import foo
else:
from _mymoduleA import *
在这种情况下 myapp/mypackage/tests/test_all.py
代码变得更简单一些:
import importlib
import unittest
if not importlib.util.find_spec('mypackage'):
from __init__ import *
import mypackage
from mypackage import mymoduleB
class TestAll(unittest.TestCase):
def test_A(self):
self.assertEqual(1, mypackage.foo())
def test_B(self):
self.assertEqual(2, mymoduleB.bar())
所有其他文件保持不变。
因此,您可以从 /myapp
和 /myapp/mypackage
文件夹中进行 运行 测试。同时,不需要硬编码任何绝对路径。该应用程序可以复制到任何其他文件系统位置。
希望对你有用。
也许我的目标和我在这里尝试做的事情在 unpythonic 的意义上是错误的。我愿意就此提出任何建议。
我的目标
- 具有自己的测试文件夹的应用程序 (
myapp
)。 - 一个包(
mypackage
)有自己的测试文件夹。 - 包的测试应该运行能够从应用程序文件夹和包文件夹中进行。
- 该包具有隐式和显式组件。后者需要显式导入(例如通过
import mypackage.mymoduleB
)。 - 可以将包(文件夹)复制(发送以在其他应用程序中重用?)到其他文件系统位置,而不会失去其功能和可测试性。这就是为什么
tests
在包文件夹内而不是在包文件夹外的原因。
这是文件夹树,其中 itest
是项目名称,myapp
是其中包含 if __name__ == '__main__':
的应用程序,mypackag
是包。
itest
└── myapp
├── myapp.py
├── mypackage
│ ├── __init__.py
│ ├── _mymoduleA.py
│ ├── mymoduleB.py
│ └── tests
│ ├── __init__.py
│ └── test_all.py
└── tests
├── __init__.py
└── test_myapp.py
问题
我可以 运行 应用程序目录中的单元测试没有问题。
/home/user/tab-cloud/_transfer/itest/myapp $ python3 -m unittest -vvv
test_A (mypackage.tests.test_all.TestAll) ... mymoduleA.foo()
ok
test_B (mypackage.tests.test_all.TestAll) ... mymoduleB.bar()
ok
test_myname (tests.test_myapp.TestMyApp) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
但是当我进入包装内部时,测试没有 运行(即目标 #3)。
/home/user/tab-cloud/_transfer/itest/myapp/mypackage $ python3 -m unittest -vvv
tests.test_all (unittest.loader._FailedTest) ... ERROR
======================================================================
ERROR: tests.test_all (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_all
Traceback (most recent call last):
File "/usr/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/usr/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/home/user/tab-cloud/_transfer/itest/myapp/mypackage/tests/test_all.py", line 12, in <module>
from . import mypackage
ImportError: cannot import name 'mypackage' from 'tests' (/home/user/tab-cloud/_transfer/itest/myapp/mypackage/tests/__init__.py)
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
MWE
不,我给你看文件。为了确保在 运行 时使用正确的 import
从应用程序文件夹或我使用的包文件夹 importlib
(基于 foreign solution)对包进行测试。
三个文件组成包
这是myapp/mypackage/__init__.py
:
# imported implicite via 'mypackage'
from ._mymoduleA import *
# 'mymoduleB' need to be imported explicite
# via 'mypackage.moduleB'
这是myapp/mypackage/_mymoduleA.py
:
def foo():
print('mymoduleA.foo()')
return 1
这是myapp/mypackage/mymoduleB.py
:
def bar():
print('mymoduleB.bar()')
return 2
包的测试
myapp/mypackage/tests/__init__.py
为空。
这是myapp/mypackage/tests/test_all.py
:
import importlib
import unittest
# The package should be able to be tested by itself (run unittest inside the
# package directory) AND from the using application (run unittest in
# application directory).
# Based on:
if importlib.util.find_spec('mypackage'):
import mypackage
import mypackage.mymoduleB
else:
from . import mypackage
from mypackage import mymoduleB
class TestAll(unittest.TestCase):
def test_A(self):
self.assertEqual(1, mypackage.foo())
def test_B(self):
self.assertEqual(2, mypackage.mymoduleB.bar())
申请
这是cat myapp/myapp.py
:
#!/usr/bin/env python3
import mypackage
def myname():
return 'My application!'
if __name__ == '__main__':
print(myname())
mypackage.foo()
try:
mypackage.mymoduleB.bar()
except AttributeError:
# we expecting this
print('Not imported yet: "mymoduleB.bar()"')
# this should work
import mypackage.mymoduleB
mypackage.mymoduleB.bar()
应用程序测试
myapp/tests/__init__.py
为空。
这是myapp/tests/test_myapp.py
:
import unittest
import myapp
class TestMyApp(unittest.TestCase):
def test_myname(self):
self.assertEqual(myapp.myname(), 'My application!')
旁注
请让我解释一下我的目标。 mypackage
应该可以在其他项目中重复使用。实际上,这意味着我将 mypackage
文件夹从一个地方复制到另一个地方。在复制该文件夹时,我确实希望 tests
文件夹随附它而无需明确考虑它,因为它在包文件夹之外。如果新项目进行单元测试,则包的测试应该自动参与该单元测试(通过 discover
)。
几年前我创建了一个导入库。它适用于路径。我用它来创建一个插件系统,我基本上可以在其中安装和导入任何库的多个版本(有一些限制)。
为此,我们获取模块的当前路径。然后我们使用路径导入包。该库会自动将正确的路径添加到 sys.path.
您需要做的就是安装 pylibimp pip install pylibimp
并编辑 myapp/mypackage/tests/test_all.py
import os
import pylibimp
import unittest
path_tests = os.path.join(os.path.dirname(__file__))
path_mypackage = os.path.dirname(path_tests)
path_myapp = os.path.dirname(path_mypackage)
mypackage = pylibimp.import_module(os.path.join(path_myapp, 'mypackage'), reset_modules=False)
class TestAll(unittest.TestCase):
def test_A(self):
self.assertEqual(1, mypackage.foo())
def test_B(self):
self.assertEqual(2, mypackage.mymoduleB.bar())
我觉得背景比较简单
import os
import sys
sys.path.insert(0, os.path.abspath('path/to/myapp'))
# Since path is added we can "import mypackage"
mypackage = __import__('mypackage')
sys.path.pop(0) # remove the added path to not mess with other imports
希望这就是您要找的。
你的目标真的有点不符合Python。但有的时候,还是要打破常规才能放飞心灵。
您可以通过检查 myapp/mypackage/__init__.py
中的 __package__
属性来解决问题,如下所示:
# hint from there:
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
if __package__:
from ._mymoduleA import foo
else:
from _mymoduleA import *
在这种情况下 myapp/mypackage/tests/test_all.py
代码变得更简单一些:
import importlib
import unittest
if not importlib.util.find_spec('mypackage'):
from __init__ import *
import mypackage
from mypackage import mymoduleB
class TestAll(unittest.TestCase):
def test_A(self):
self.assertEqual(1, mypackage.foo())
def test_B(self):
self.assertEqual(2, mymoduleB.bar())
所有其他文件保持不变。
因此,您可以从 /myapp
和 /myapp/mypackage
文件夹中进行 运行 测试。同时,不需要硬编码任何绝对路径。该应用程序可以复制到任何其他文件系统位置。
希望对你有用。