How do I Pytest a project using PEP 420 namespace packages?
我正在尝试使用pytest来测试一个较大的项目(~100k loc,1k个文件),并且还有其他几个类似的项目,我最终也希望这样做。这不是标准的python包;它是一个高度定制的系统的一部分,我几乎没有能力更改它,至少在短期内是这样。测试模块与代码集成在一起,而不是在一个单独的目录中,这对我们很重要。配置与这个问题非常相似,我的答案也可能提供有用的背景。
我面临的问题是,项目几乎完全使用PEP420隐式名称空间包;也就是说,在任何包目录中几乎没有
考虑一个类似以下内容的存储库。(对于它的可运行副本,包括下面描述的测试,从Github克隆
1 2 3 4 5 | repo/ project/ util/ thing.py thing_test.py |
我对测试配置有足够的控制,可以确保
1 2 | def test_good_import(): import project.util.thing |
号
但是,Pytest使用它的常规系统从文件中确定包名称,给出不是我配置的标准包名称,并将我的项目的子目录添加到
1 2 3 4 5 6 7 8 9 10 11 | def test_modulename(): assert 'project.util.thing_test' == __name__ # Result: AssertionError: assert 'project.util.thing_test' == 'thing_test' def test_bad_import(): ''' While we have a `project.util.thing` deep in our hierarchy, we do not have a top-level `thing` module, so this import should fail. ''' with raises(ImportError): import thing # Result: Failed: DID NOT RAISE <class 'ImportError'> |
如您所见,虽然
这带来了许多问题:
我想我能做的是告诉Pytest,它应该相对于我提供的特定文件系统路径来确定模块名,而不是根据存在和不存在
第三种选择(在适应了当前的情况并改变了如上所述的pytest之后)是简单地向项目中添加几十个
是否有方法改进上述选项?我还有其他的选择吗?
您面临的问题是,您将测试放在名称空间包内的生产代码旁边。如本文所述,
Standalone test modules / conftest.py files
...
pytest will find
foo/bar/tests/test_foo.py and realize it is NOT part
of a package given that there’s no__init__.py file in the same folder. It will then addroot/foo/bar/tests tosys.path in order to importtest_foo.py as the moduletest_foo . The same is done with theconftest.py file by addingroot/foo tosys.path to import it asconftest .
号
因此,解决(至少部分)这一问题的正确方法是调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # repo/conftest.py import pathlib import py._path.local # the original pypkgpath method can't deal with namespace packages, # considering only dirs with __init__.py as packages pypkgpath_orig = py._path.local.LocalPath.pypkgpath # we consider all dirs in repo/ to be namespace packages rootdir = pathlib.Path(__file__).parent.resolve() namespace_pkg_dirs = [str(d) for d in rootdir.iterdir() if d.is_dir()] # patched method def pypkgpath(self): # call original lookup pkgpath = pypkgpath_orig(self) if pkgpath is not None: return pkgpath # original lookup failed, check if we are subdir of a namespace package # if yes, return the namespace package we belong to for parent in self.parts(reverse=True): if str(parent) in namespace_pkg_dirs: return parent return None # apply patch py._path.local.LocalPath.pypkgpath = pypkgpath |