Python unittest
模块似乎假设一个项目的目录结构,其中有一个项目根级目录,其中包含源代码和该目录下的测试。
但是,我希望在我的~/bin
目录中编写Python脚本,并在另一个目录(例如~/dev/tests
)中对其进行测试。有没有办法让我使用命令行界面运行单元测试而不设置我的PYTHONPATH
环境变量并创建__init__.py
文件等等?
这是一个展示我想要的简单例子:
~/bin/candy
:
#!/usr/bin/env python
def candy():
return "candy"
if __name__ == '__main__':
print candy()
~/dev/tests/test_candy.py
:
#!/usr/bin/env python
import unittest
import candy
class CandyTestCase(unittest.TestCase):
def testCandy(self):
candyOutput = candy.candy()
assert candyOutput == "candy"
我注意到在以下情况下可以方便地完成所有工作:
candy.py
和test_candy.py
)我可以使用unittest
模块运行python来执行以下操作,而无需在我的环境中明确设置任何内容:
~/candy
)。test_candy
是否有py作为扩展名。candy
和test_candy.py
不共享公共根目录(我的主目录除外)。如果通过python -m unittest
的简单调用无法做到这一点,那么实现这一目标的最简单方法是什么?
答案 0 :(得分:3)
这是Candy可执行文件(不变):
➜ cat ~/bin/candy
#!/usr/bin/env python
def candy():
return "candy"
if __name__ == '__main__':
print candy()
这是~/dev/tests/test_candy.py
(已更改):
➜ cat ~/dev/tests/test_candy.py
#!/usr/bin/env python
import imp
import unittest
from os.path import expanduser, join
# use expanduser to locate its home dir and join bin and candy module paths
candy_module_path = join(expanduser("~"), "bin", "candy")
# load the module without .py extension
candy = imp.load_source("candy", candy_module_path)
class CandyTestCase(unittest.TestCase):
def testCandy(self):
candyOutput = candy.candy()
assert candyOutput == "candy"
发生了什么变化?
我们添加了imp.load_source
来导入~/bin/candy
(扩展名为*.py
的模块)
我们增加了使用~
expanduser
我们正在使用os.path.join
来加入~/bin/candy
现在,您可以使用discover
模块的unittest
选项运行测试。
检查python -m unittest --help
以获得更多详细信息。
以下摘录
-s directory Directory to start discovery ('.' default)
-p pattern Pattern to match test files ('test*.py' default)
➜ python -m unittest discover -s ~/bin/ -p 'test*' -v ~/dev/tests
testCandy (test_candy.CandyTestCase) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
答案 1 :(得分:1)
从Python 3.3开始,imp
软件包已被弃用,而importlib
软件包将其替换。 This answer详细介绍了如何导入单个文件。
对于您的单元测试,应为:
from importlib.machinery import ModuleSpec, SourceFileLoader
from importlib.util import spec_from_loader, module_from_spec
import os.path
import types
import unittest
def import_from_source( name : str, file_path : str ) -> types.ModuleType:
loader : SourceFileLoader = SourceFileLoader(name, file_path)
spec : ModuleSpec = spec_from_loader(loader.name, loader)
module : types.ModuleType = module_from_spec(spec)
loader.exec_module(module)
return module
script_path : str = os.path.abspath(
os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "..", "bin", "candy",
)
)
candy : types.ModuleType = import_from_source("candy", script_path)
class CandyTestCase(unittest.TestCase):
def testCandy(self : "CandyTestCase" ) -> None:
self.assertEqual( candy.candy(), "candy" )
if __name__ == '__main__':
unittest.main()
假设文件结构为:
base_directory/bin/candy
base_directory/dev/tests/test_candy.py
(注意:此单元测试采用测试的固定相对路径,而不是固定的绝对路径,因此您可以将包移动到另一个目录,并且只要包中的文件不中断测试,不要更改相对位置。)
答案 2 :(得分:0)
我还没有在unittest上尝试过,但是对于这些问题,我的快速解决方案是只使用os模块在脚本内更改我的工作目录。但这应该为您工作。
#!/usr/bin/env python
import unittest
import os
os.chdir("/usr/bin/candy")
import candy
class CandyTestCase(unittest.TestCase):
def testCandy(self):
candyOutput = candy.candy()
assert candyOutput == "candy"
答案 3 :(得分:0)
简短回答,不。问题在于您需要导入要测试的模块,因此至少您必须更改PYTHONPATH。我的建议是在执行测试时临时更改它,如下所示:
import sys
sys.path.extend(['~/dir1','~/dir2','~/anotherdir'])
@Paritosh_Singh的解决方案很好。
从长远来看,是要安装像 tox 这样的测试运行程序并对其进行配置,以使其“看到”您的模块。但我认为您不想这样做。