第 8 章 配置文件
配置文件—那些影响 pytest 运行方式的非测试文件—可以节省时间和重复工作。如果你发现自己总是在测试中使用某些标志,比如 --verbose
或 --strict-markers
,你可以把它们藏在配置文件中,而不必总是键入它们。除了配置文件,还有一些其他文件在使用 pytest 时也很有用,它们能让编写和运行测试变得更容易。我们将在本章介绍所有这些文件。
了解pytest配置文件
让我们来看看与 pytest 相关的非测试文件:
-
pytest.ini:这是主要的 pytest 配置文件,允许您更改 pytest 的默认行为。它的位置还定义了 pytest 根目录或 rootdir。
-
conftest.py:该文件包含固定装置和挂钩函数。它可以存在于 rootdir 或任何子目录中。
-
__init__.py
:当放入测试子目录时,该文件允许您在多个测试目录中具有相同的测试文件名。 -
tox.ini、pyproject.toml 和 setup.cfg:这些文件可以代替 pytest.ini。 如果您的项目中已有这些文件之一,则可以使用它来保存 pytest 设置。
-
tox.ini 由 tox 使用,tox 是我们在第 151 页第 11 章 “tox 和持续集成” 中介绍的命令行自动化测试工具。
-
pyproject.toml 用于打包 Python 项目,可用于保存 各种工具的设置,包括 pytest。
-
setup.cfg 也用于打包,可用于保存 pytest 设置。
-
让我们在示例项目目录结构的上下文中查看其中一些文件:
cards_proj
├── ... top level project files, src dir, docs, etc ...
├── pytest.ini
└── tests
├── conftest.py
├── api
│ ├── __init__.py
│ ├── conftest.py
│ └── ... test files for api ...
└── cli
├── __init__.py
├── conftest.py
└── ... test files for cli ...
在我们目前使用的 Cards 项目中,没有测试目录。不过,无论是开源项目还是闭源项目,测试通常都存在于项目的 tests 目录中。
我们将在本节的其余部分讨论各种文件时参考这一结构。
在 pytest.ini 中保存设置和标志
让我们看一个 pytest.ini 文件示例:
[pytest]
addopts =
--strict-markers
--strict-config
-ra
testpaths = tests
markers =
smoke: subset of tests
exception: check for expected exceptions
该文件以 [pytest] 开头,表示 pytest 设置的开始。 考虑到它严格来说是一个 pytest 配置文件,我们必须包含这个符号可能看起来很奇怪。 但是,包含 [pytest] 允许 pytest ini 解析以相同的方式处理 pytest.ini 和 tox.ini。 之后是单独的设置,每个设置都在自己的行(或多行)中,格式为 <setting> = <value>。
允许多个值的配置设置通常允许将值写入一行或多行。 例如,我们可以将选项全部写在一行上,如下所示:
addopts = --strict-markers --strict-config -ra
将它们分成每个标志一行是一种风格。 标记不同,每行只允许有一个标记。
这个示例是一个基本的 pytest.ini 文件,其中包含我几乎总是设置的项目。 让我们简要介绍一下选项和设置:
-
addopts = --strict-markers --strict-config -ra
-
addopts 设置使我们能够列出我们始终希望在该项目中运行的 pytest 标志。
-
--strict-markers 告诉 pytest 对于测试代码中遇到的任何未注册标记引发错误而不是警告。打开此选项以避免标记名称拼写错误。
-
--strict-config 告诉 pytest 在解析配置文件时遇到任何困难都会引发错误。默认是警告。打开此选项以避免配置文件拼写错误被忽视。
-
-ra 告诉 pytest 在测试运行结束时显示额外的测试摘要信息。默认设置是仅显示有关测试失败和错误的额外信息。-ra 的 a 部分告诉 pytest 显示除通过测试之外的所有信息。这会将 skipped、xfailed 和 xpassed 添加到失败和错误测试中。
-
-
testpaths = tests
-
testpaths 设置告诉 pytest 如果您没有在命令行上给出文件或目录名,则在哪里查找测试。 将 testpaths 设置为测试告诉 pytest 在测试目录中查找。
-
乍一看,为测试设置 testpath 似乎是多余的,因为 pytest 无论如何都会在那里查找,并且我们的 src 或 docs 目录中没有任何 test_ 文件。 但是,指定 testpaths 目录可以节省一些启动时间,特别是当我们的 docs 或 src 或其他目录非常大时。
-
-
markers = …
-
标记设置用于声明标记,就像我们在 【第 79 页的使用自定义标记选择测试】 中所做的那样。
-
您可以在配置文件中指定更多配置设置和命令行选项,并且可以通过运行 pytest --help
来查看所有这些设置和命令行选项。
使用 tox.ini,pyproject.toml 或 setup.cfg 代替 pytest.ini
如果您正在为已经具有 pyproject.toml、tox.ini 或 setup.cfg 文件的项目编写测试,您仍然可以使用 pytest.ini 来存储 pytest 配置设置。 或者,您可以将配置设置存储在这些备用配置文件之一中。 两个非 ini 文件的语法略有不同,因此我们将逐一查看。
tox.ini
tox.ini 文件包含 tox 的设置,【第 11 章 “tox 和持续集成”(第 151 页)】 对此进行了更详细的介绍。它还可以包含 [pytest] 部分。 因为它也是一个 .ini
文件,所以下面的 tox.ini 示例几乎与前面显示的 pytest.ini 示例相同。 唯一的区别是还会有一个 [tox] 部分。
示例 tox.ini 文件如下所示:
;---
[tox]
; tox specific settings
[pytest]
addopts =
--strict-markers
--strict-config
-ra
testpaths = tests
markers =
smoke: subset of tests
exception: check for expected exceptions
pyproject.toml
pyproject.toml 文件最初是一个用于打包 Python 项目的文件;然而,Poetry 和 Flit 项目使用 pyproject.toml 来定义项目设置。在 Flit 和 Poetry 出现之前,Setuptools 库一直是标准的打包工具,传统上不使用 pyproject.toml。
不过,现在你可以将 Setuptools 与 pyproject.toml 结合使用了。2018 年,一种名为 Black 的 Python 代码格式化工具开始流行起来。配置 Black 的唯一方法就是使用 pyproject.toml。从那时起,越来越多的工具开始支持在 pyproject.toml 中存储配置,包括 pytest。
由于 TOML 是一种不同于 .ini 文件的配置文件标准,因此格式略有不同,但相当容易上手。格式如下:
[tool.pytest.ini_options]
addopts = [
"--strict-markers",
"--strict-config",
"-ra"
]
testpaths = "tests"
markers = [
"smoke: subset of tests",
"exception: check for expected exceptions"
]
您可以使用 [tool.pytest.ini_options] 开始该部分,而不是 [pytest]。 设置值需要用引号引起来,并且设置值列表需要是括号中的字符串列表。
setup.cfg
setup.cfg 文件格式更像是 .ini。这是我们的配置示例 setup.cfg 文件的样子:
[tool:pytest]
addopts =
--strict-markers
--strict-config
-ra
testpaths = tests
markers =
smoke: subset of tests
exception: check for expected exceptions
在这里,它和 pytest.ini 之间唯一明显的区别是 [tool:pytest] 的部分说明符。
然而,pytest 文档警告说 .cfg
解析器与 .ini
文件解析器不同,并且这种差异可能会导致难以追踪的问题。
确定根目录和配置文件
甚至在开始寻找要运行的测试文件之前,pytest 就会读取配置文件 --pytest.ini 文件或包含 pytest 部分的 tox.ini、setup.cfg 或 pyproject.toml 文件。
如果传入了测试目录,pytest 就会从那里开始查找。如果输入了多个文件或目录,pytest 会从它们的共同祖先开始查找。如果没有传入文件或目录,则从当前目录开始。如果 pytest 在起始目录中找到了配置文件,那就是根目录。如果没有,pytest 就会沿着目录树向上查找,直到找到包含 pytest 部分的配置文件。一旦 pytest 找到了配置文件,它就会把找到文件的目录标记为根目录,即 rootdir。这个根目录也是测试节点 ID 的相对根目录。它还会告诉你在哪里找到了配置文件。
关于使用哪个配置文件以及根目录在哪里的规则,一开始可能会让人感到困惑。然而,有了一个定义明确的根目录搜索过程,并让 pytest 显示根目录的位置,我们就可以在不同层次上运行测试,并确保 pytest 能找到正确的配置文件。例如,即使你把目录改到了测试目录深处的测试子目录,pytest 仍然能在项目顶层找到你的配置文件。
即使你不需要任何配置设置,在项目顶层放置一个空的 pytest.ini 也是个好主意。如果没有任何配置文件,pytest 会一直搜索文件系统的根目录。最好的情况是,这只会在 pytest 搜索时造成轻微延迟。最坏的情况是,它会一路找到一个与你的项目无关的配置文件。
一旦找到配置文件,pytest 就会在测试运行的顶端打印出使用的根目录和配置文件:
$ cd /path/to/code/ch8/project
$ pytest
========================= test session starts ==========================
platform darwin -- Python 3.x.y, pytest-x.y.z, py-1.x.y, pluggy-0.x.y
➤ rootdir: /path/to/code/ch8/project, configfile: pytest.ini, testpaths: tests
collected 28 items
tests/api/test_add.py ..... [ 17%]
tests/api/test_config.py . [ 21%]
...
tests/api/test_update.py .... [ 96%]
tests/api/test_version.py . [100%]
========================== 28 passed in 0.14s ==========================
如果您设置了 testpaths,它还会显示测试路径,我们就是这样做的。 那很好。
请注意,对于本书中的大多数示例,您不会看到此标题信息,因为它被删除的唯一目的是使示例更短且更易于阅读。
与 conftest.py 共享本地夹具和钩子函数
conftest.py 文件用于存储固定装置和钩子函数。(固定装置在第 3 章 pytest 固定装置中介绍,钩子函数在第 15 章构建插件中讨论。) 在一个项目中,你可以拥有任意多个 conftest.py 文件,甚至每个测试子目录都可以有一个。在 conftest.py 文件中定义的任何内容都适用于该目录和所有子目录中的测试。
如果在测试层有一个顶级的 conftest.py 文件,那么该文件中定义的固定装置就可以用于顶级测试目录及其下的所有测试。如果有特定的固定装置只适用于某个子目录,则可以在该子目录下的另一个 conftest.py 文件中定义这些固定装置。例如,GUI 测试可能需要与 API 测试不同的固定装置,也可能需要共享一些固定装置。
不过,最好还是坚持使用一个 conftest.py 文件,这样就能轻松找到固定装置定义。尽管你可以通过 pytest --fixtures -v 找到夹具定义的位置,但如果你知道它在你正在查看的测试文件中,或者在另一个文件(conftest.py 文件)中,那就更容易了。
避免测试文件名称碰撞
__init__.py
文件以一种方式影响 pytest,并且仅以一种方式:它允许您拥有重复的测试文件名。
如果每个测试子目录中都有 __init__.py
文件,则可以在多个目录中显示相同的测试文件名。 这就是拥有 __init__.py
文件的唯一原因。
这是一个例子:
$ cd /path/to/code/ch8/dup
$ tree tests_with_init
tests_with_init
├── api
│ ├── __init__.py
│ └── test_add.py
├── cli
│ ├── __init__.py
│ └── test_add.py
└── pytest.ini
我们可能想通过 API 和 CLI 测试一些添加功能,因此在两者中都有一个 test_add.py 似乎是合理的。
只要我们在 api 和 cli 目录中都有一个 __init__.py
文件,这个测试就可以正常工作:
$ pytest -v tests_with_init
========================= test session starts ==========================
collected 2 items
tests_with_init/api/test_add.py::test_add PASSED [ 50%]
tests_with_init/cli/test_add.py::test_add PASSED [100%]
========================== 2 passed in 0.02s ===========================
但是,如果我们省略 __init__.py
文件,它将无法工作。 这是同一目录,没有 __init__.py
文件:
$ tree tests_no_init
tests_no_init
├── api
│ └── test_add.py
├── cli
│ └── test_add.py
└── pytest.ini
当我们尝试运行测试时,出现错误:
$ pytest -v tests_no_init
========================= test session starts ==========================
collected 1 item / 1 error
================================ ERRORS ================================
___________________ ERROR collecting cli/test_add.py ___________________
import file mismatch:
imported module 'test_add' has this __file__ attribute:
/path/to/code/ch8/dup/tests_no_init/api/test_add.py
which is not the same as the test file we want to collect:
/path/to/code/ch8/dup/tests_no_init/cli/test_add.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for
your test file modules
======================= short test summary info ========================
ERROR tests_no_init/cli/test_add.py
!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!
=========================== 1 error in 0.07s ===========================
该错误消息强调我们有两个名称相同的文件,并建议更改文件名。 更改文件名可以避免此错误,但您也可以添加 __init__.py
文件并保持原样。
文件名重复是一个很容易引起混淆的错误,因此,把 __init__.py
文件放在子目录中就可以了。
回顾
在本章中,我们查看了所有与测试相关但不是测试文件的文件:
-
您可以在每个项目的一个主配置文件中包含 pytest 设置:pytest.ini、pyproject.toml、tox.ini 或 setup.cfg。
-
pytest 将主配置文件位置称为根目录或 rootdir。
-
设置存在于配置文件中,包括由 addopts 配置设置定义的选项和标志。
-
conftest.py 文件用于同一目录或更低目录中的所有测试共享的固定装置和挂钩函数。
-
test 子目录中的
__init__.py
文件允许您复制测试文件名。
练习
现在习惯添加和编辑配置文件将帮助您了解它们是多么简单和强大。 这些练习侧重于主要配置文件。
以下练习基于 /path/to/code/exercises/ch8 目录,如下所示:
exercises/ch8
├── pytest.ini
└── tests
├── a
│ └── test_x.py
└── b
└── test_x.py
-
转到 /path/to/code/exercises/ch8 并运行 pytest。
-
什么是根目录?
-
使用的配置文件是什么?
-
您还应该看到一条错误消息。 它说什么?
-
-
在 pytest.ini 文件中,将测试路径设置为 tests/a。
-
这能解决错误吗?
-
-
将测试路径从 tests/a 更改为 tests。 将
__init__.py
文件添加到 a 和 b 中。-
这能解决错误吗?
-
-
将 addopts 设置为 -v 并重新运行 pytest。
-
行为改变是什么?
-
-
创建一个 tests/pyproject.toml 文件。
-
将 addopts 设置为 “-v”。
-
从 exercise/ch8 目录运行 pytest,并从 exercise/ch8/tests 目录运行一次。
-
根目录和配置文件是否不同?
-
如果是,为什么?
-