对于一个简单的Python模块来说,非常常见的目录结构似乎是将单元测试分离到它们自己的测试目录中:
new_project/
antigravity/
antigravity.py
test/
test_antigravity.py
setup.py
etc.
我的问题很简单,实际运行测试的通常方式是什么?我怀疑这对每个人来说都是显而易见的,除了我,但你不能只是从测试目录运行python test_antigravity.py,因为它的导入antigravity将失败,因为模块不在路径上。
我知道我可以修改PYTHONPATH和其他与搜索路径相关的技巧,但我不能相信这是最简单的方法——如果您是开发人员,这很好,但如果用户只是想检查测试是否通过,那么期望他们使用这种方法是不现实的。
另一种替代方法是将测试文件复制到另一个目录中,但这似乎有点愚蠢,并且没有注意到将它们放在一个单独的目录中。
那么,如果您刚刚下载源代码到我的新项目,您将如何运行单元测试?我更喜欢这样的答案:“要运行单元测试,请执行x。”
Python 3 +
添加到@Pierre
使用这样的unittest目录结构:
new_project
├── antigravity
│ ├── __init__.py # make it a package
│ └── antigravity.py
└── test
├── __init__.py # also make test a package
└── test_antigravity.py
运行测试模块test_antigravity.py:
$ cd new_project
$ python -m unittest test.test_antigravity
或者一个单独的TestCase
$ python -m unittest test.test_antigravity.GravityTestCase
强制性的不要忘记__init__.py,即使为空,否则将无法工作。
我通常在项目目录中创建一个“运行测试”脚本(对于源目录和测试都是通用的),用于加载我的“所有测试”套件。这通常是样板代码,所以我可以在项目之间重用它。
run_tests.py:
import unittest
import test.all_tests
testSuite = test.all_tests.create_test_suite()
text_runner = unittest.TextTestRunner().run(testSuite)
test/all_tests.py(来自如何在一个目录中运行所有Python单元测试?)
import glob
import unittest
def create_test_suite():
test_file_strings = glob.glob('test/test_*.py')
module_strings = ['test.'+str[5:len(str)-3] for str in test_file_strings]
suites = [unittest.defaultTestLoader.loadTestsFromName(name) \
for name in module_strings]
testSuite = unittest.TestSuite(suites)
return testSuite
有了这个设置,你确实可以在你的测试模块中包含反重力。缺点是你需要更多的支持代码来执行一个特定的测试…我每次都把它们都运行一遍。
可以使用包装器来运行选定的或所有测试。
例如:
./run_tests antigravity/*.py
或者递归地运行所有测试使用globbing (tests/**/*.py)(通过shop -s globstar启用)。
包装器基本上可以使用argparse来解析参数,比如:
parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*')
然后加载所有测试:
for filename in args.files:
exec(open(filename).read())
然后将它们添加到你的测试套件中(使用inspect):
alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and name.startswith("FooTest"):
alltests.addTest(unittest.makeSuite(obj))
并运行它们:
result = unittest.TextTestRunner(verbosity=2).run(alltests)
查看这个示例了解更多细节。
请参见:如何在一个目录中运行所有Python单元测试?
Python unittest模块的解决方案/示例
鉴于以下项目结构:
ProjectName
├── project_name
| ├── models
| | └── thing_1.py
| └── __main__.py
└── test
├── models
| └── test_thing_1.py
└── __main__.py
你可以使用python project_name从根目录运行你的项目,它调用ProjectName/project_name/__main__.py。
要使用python test运行测试,有效地运行ProjectName/test/__main__.py,您需要执行以下操作:
1)通过添加__init__.py文件将您的test/models目录转换为一个包。这使得子目录中的测试用例可以从父测试目录中访问。
# ProjectName/test/models/__init__.py
from .test_thing_1 import Thing1TestCase
2)在test/__main__.py中修改系统路径以包含project_name目录。
# ProjectName/test/__main__.py
import sys
import unittest
sys.path.append('../project_name')
loader = unittest.TestLoader()
testSuite = loader.discover('test')
testRunner = unittest.TextTestRunner(verbosity=2)
testRunner.run(testSuite)
现在您可以在测试中成功地从project_name导入内容。
# ProjectName/test/models/test_thing_1.py
import unittest
from project_name.models import Thing1 # this doesn't work without 'sys.path.append' per step 2 above
class Thing1TestCase(unittest.TestCase):
def test_thing_1_init(self):
thing_id = 'ABC'
thing1 = Thing1(thing_id)
self.assertEqual(thing_id, thing.id)
在我看来,最好的解决方案是使用unittest命令行界面,它会将目录添加到sys. exe目录。路径,因此您不必(在TestLoader类中完成)。
例如,对于这样的目录结构:
new_project
├── antigravity.py
└── test_antigravity.py
你可以直接运行:
$ cd new_project
$ python -m unittest test_antigravity
对于像你这样的目录结构:
new_project
├── antigravity
│ ├── __init__.py # make it a package
│ └── antigravity.py
└── test
├── __init__.py # also make test a package
└── test_antigravity.py
在测试包中的测试模块中,可以像往常一样导入反重力包及其模块:
# import the package
import antigravity
# import the antigravity module
from antigravity import antigravity
# or an object inside the antigravity module
from antigravity.antigravity import my_object
运行单个测试模块:
要运行单个测试模块,在本例中为test_antigravity.py:
$ cd new_project
$ python -m unittest test.test_antigravity
引用测试模块的方法与导入测试模块的方法相同。
运行单个测试用例或测试方法:
你也可以运行一个TestCase或者一个测试方法:
$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method
运行所有测试:
你也可以使用测试发现,它会发现并运行所有的测试,它们必须是命名为test*.py的模块或包(可以使用-p,——pattern标志进行更改):
$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest
这将运行测试包中的所有test*.py模块。