我应该如何构建一个 pytest 测试包?

How should I structure a pytest test only package?

我的用例是关于远程(RESTful API 等)测试整个系统的子系统。这意味着 "pytest test only package" 对生产代码没有任何依赖性(意味着其他生产代码 python 包)。

我创建了一个 python 包,它只包含测试相关的东西,如 pytest 测试、pytest fixtures、util 模块中的测试辅助函数、pytests conftest.py、pytests pytest.ini 等。它不包含任何与生产代码相关的内容。

目前功能正常,但包的结构相当 "hacky"。这意味着安装工作不正常(测试、fixture 和 conftest 文件未通过 MANIFEST.ini 正确安装到 site_packages)并且必须完成包的部署 "manually".

在 pytest 文档中,我刚刚找到了有关如何构建包含生产和 pytest 测试代码的包的最佳实践:Tests outside application code, Tests as part of application code and Test directory structure

  1. 我应该如何构建只包含测试代码的 python 包?是否有替代的包结构(优点,缺点)?
  2. 测试文件及其依赖项(fixtures、helpers 等)应该安装到哪里?

2 的可能解决方案:avocado-framework 部署 example tests as data files in setup.py. Dependent on the configuration 测试默认部署到 /usr/share/avocado/tests

要求

在我看来,您需要满足三个要求:

  1. 安装的测试应该很容易被发现
  2. 出于开发目的,测试仍应运行在本地可用
  3. 运行 开发模式和安装模式下的测试应该产生相同的结果

项目结构

考虑到这一点,我将如何构建项目:创建一些虚拟包(但具有唯一的可区分名称)并将测试目录以及 conftest.py 和实用程序模块放在那里。这是它的样子:

project/
├── setup.py
└── mypkg/
    ├── __init__.py
    └── tests/
        ├── conftest.py
        ├── utils.py
        ├── other_utils.py
        ├── test_spam.py
        ├── test_eggs.py
        └── other_tests/  # if you need to further grouping of tests
            ├── conftest.py  # if you need special conftest for other tests
            └── test_bacon.py

setup.py

from setuptools import setup

setup(
    name='mypkg-tests',
    version='0.1',
    install_requires=['pytest'],
    packages=['mypkg'],
    package_data={'mypkg': ['tests/*', 'tests/**/*']},
)

使用此项目结构:

  1. project/ 目录中本地正确发现和执行测试:

    $ pytest -v
    ============================= test session starts =============================
    platform darwin -- Python 3.6.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 -- /Users
    /hoefling/.virtualenvs/Whosebug/bin/python
    cachedir: .cache
    rootdir: /Users/hoefling/projects/private/Whosebug/so-48111426, inifile:
    plugins: forked-0.2, asyncio-0.8.0, xdist-1.22.0, mock-1.6.3, hypothesis-3.44.4
    collected 3 items
    
    spam/tests/test_eggs.py::test_foo PASSED                                 [ 33%]
    spam/tests/test_spam.py::test_bar PASSED                                 [ 66%]
    spam/tests/other_tests/test_bacon.py::test_baz PASSED                    [100%]
    
    ========================== 3 passed in 0.03 seconds ===========================
    
  2. 当您构建源 tar 或 wheel 并安装包时,通过提供包名称可以轻松 运行 测试:

    $ pytest -pyargs mypkg
    ...
    

    您将获得完全相同的测试结果,因为 pytest 发现测试的方式与 运行 在本地进行测试的方式相同,只不过不是扫描当前工作目录,而是包的将扫描目录。

  3. 尽管所有测试以及配置和实用程序都安装在 site-packages 中,但它们本身不是包或模块。对于外界来说,分发包中只有一个空包mypkgtests/ 中的任何内容都不可导入,并且仅对 pytest.

  4. 可见

为什么data_files不可靠

当然你可以在设置脚本中这样声明:

setup(
    ...
    data_files=[
        ('tests', ['tests/test_spam.py', 'tests/test_eggs.py']),  # etc
    ]
)

首先,创建和维护要包含的测试列表并不方便(尽管肯定可以使用 os.walkpathlib.glob 或任何)。但更重要的是,您将无法可靠地将 data_files 安装到绝对路径。我想在这里详细介绍;随时查看 以获取更多信息 - 但基本上,它是 wheel 包相对化 data_filessys.prefix 中的每个绝对路径,即使你构建了一个源distribution,pip install 将首先用它构建一个轮子,然后安装轮子。因此,如果你想 运行 从已安装的包中进行测试,你首先需要确定 sys.prefix 并自己构建路径:

$ SYS_PREFIX=$(python -c "import sys; print(sys.prefix)")
$ pytest -v $SYS_PREFIX/whatever/dir/mapped/in/data_files/tests

避免 wheel 问题的唯一方法是构建源分发并使用 pip install mypkg --no-binary=mypkg 安装它。此选项将强制 pip 跳过 wheel 构建步骤并直接从源安装。只有这样,测试才会安装到绝对路径。我觉得这很不方便,因为有时您会忘记 no-binary arg,并且会花时间寻找错误源。或者有人必须在您不在时指导他安装软件包,并且将无法 运行 测试。根本不要使用 data_files.