配置文件简介
配置文件--那些影响pytest运行的非测试文件,可以节省时间和重复工作。如果你发现自己在测试中总是使用某些标志,比如 --verbose 或 --strict-markers,你可以把它们藏在一个配置文件中,而不必总是输入它们。除了配置文件,还有一些其他文件在使用pytest时很有用,可以使编写和运行测试的工作更容易。我们将在本章中介绍所有这些文件。
了解 pytest 配置文件
- pytest.ini
这是主要的pytest配置文件,允许你改变pytest的默认行为。它的位置也定义了pytest的根目录,或称rootdir。
- conftest.py
这个文件包含固定装置和钩子函数。它可以存在于rootdir或任何子目录中。
- init.py
当放在测试子目录中时,这个文件允许你在多个测试目录中拥有相同的测试文件名。
- tox.ini, pyproject.toml, and setup.cfg:
这些文件可以代替 pytest.ini。如果你在项目中已经有这些文件之一,你可以用它来保存pytest的设置。
- tox.ini
tox使用的,它是我们在第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项目的测试中,没有测试目录。然而,无论是开源还是闭源项目,测试通常存在于项目的测试目录中。
在pytest.ini中保存设置和标志
让我们看看一个pytest.ini文件的例子。
ch8/project/pytest.ini
$ cat project/pytest.ini
;---
; Excerpted from "Python Testing with pytest, Second Edition",
; published by The Pragmatic Bookshelf.
; Copyrights apply to this code. It may not be used to create training material,
; courses, books, articles, and the like. Contact us if you are in doubt.
; We make no guarantees that this code is fit for any purpose.
; Visit https://pragprog.com/titles/bopytest2 for more book information.
;---
[pytest]
addopts =
--strict-markers
--strict-config
-ra
testpaths = tests
markers =
smoke: subset of tests
exception: check for expected exceptions
该文件以[pytest]开头,表示pytest设置的开始
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设置告诉pytest在哪里寻找测试,如果你在命令行中没有给出文件或目录名称。将testpaths设置为test,告诉pytest在test目录中寻找。
乍一看,将testpaths设置为test似乎是多余的,因为pytest无论如何都会在那里寻找,而且我们的src或docs目录中没有任何test_文件。然而,指定testpaths目录可以节省一些启动时间,特别是当我们的docs或src或其他目录相当大时。
markers设置是用来声明标记的,就像我们在《用自定义标记选择测试》中做的那样。
你可以在配置文件中指定更多的配置设置和命令行选项,你可以通过运行pytest --help看到所有的配置。
使用 tox.ini, pyproject.toml, 或 setup.cfg 来代替 pytest.ini
如果你为一个已经有pyproject.toml, tox.ini, 或setup.cfg文件的项目编写测试,你仍然可以使用pytest.ini来存储你的pytest配置设置。或者你也可以将你的配置设置存储在这些备用的配置文件中。这两个非ini文件的语法有些不同,所以我们将分别看一下。
tox.ini文件包含了对tox的设置,这在第11章 tox和持续集成中会有更详细的介绍。它也可以包括一个[pytest]部分。因为它也是一个.ini文件,下面的tox.ini例子与前面的pytest.ini例子几乎相同。唯一不同的是,也会有一个[tox]部分。
一个样本的 tox.ini 文件看起来像这样。
ch8/alt/tox.ini
;---
; Excerpted from "Python Testing with pytest, Second Edition",
; published by The Pragmatic Bookshelf.
; Copyrights apply to this code. It may not be used to create training material,
; courses, books, articles, and the like. Contact us if you are in doubt.
; We make no guarantees that this code is fit for any purpose.
; Visit https://pragprog.com/titles/bopytest2 for more book information.
;---
[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 文件开始是一个用于打包 Python 项目的文件;然而,Poetry 和 Flit项目使用 pyproject.toml 来定义项目设置。Setuptools 库,在 Flit 和 Poetry 出现之前一直是标准的打包工具,传统上没有使用 pyproject.toml。然而,现在你可以使用Setuptools与pyproject.toml。2018年,一个名为Black的Python代码格式化器开始流行起来。配置Black的唯一方法是使用pyproject.toml。从那时起,越来越多的工具开始支持将配置存储在 pyproject.toml 中,包括 pytest。
因为TOML是一个不同于.ini文件的配置文件标准,其格式有些不同,但相当容易习惯。格式看起来像这样。
ch8/alt/pyproject.toml
[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文件的格式更像.ini。下面是我们的配置例子中setup.cfg文件的样子。
ch8/alt/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就会在测试运行的顶部打印出它所使用的rootdir和配置文件。
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch8\project, configfile: pytest.ini, testpaths: tests
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0
collected 28 items
tests\api\test_add.py ..... [ 17%]
tests\api\test_config.py . [ 21%]
tests\api\test_count.py ... [ 32%]
tests\api\test_delete.py ... [ 42%]
tests\api\test_finish.py .... [ 57%]
tests\api\test_list.py .. [ 64%]
tests\api\test_path.py . [ 67%]
tests\api\test_start.py .... [ 82%]
tests\api\test_update.py .... [ 96%]
tests\api\test_version.py . [100%]
============================= 28 passed in 0.71s ==============================
如果你设置了测试路径,它还会显示测试路径,我们也设置了。这很好。
用conftest.py共享本地固定装置和钩子函数
conftest.py文件用于存储固定装置和钩子函数。你可以在一个项目中拥有任意多的 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 =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
rootdir: D:\code\pytest_quick\ch8\dup\tests_with_init, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0
collecting ... 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.09s ==============================
$ pytest -v tests_no_init
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
rootdir: D:\code\pytest_quick\ch8\dup\tests_no_init, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0
collecting ... collected 1 item / 1 error
=================================== ERRORS ====================================
______________________ ERROR collecting cli/test_add.py _______________________
import file mismatch:
imported module 'test_add' has this __file__ attribute:
D:\code\pytest_quick\ch8\dup\tests_no_init\api\test_add.py
which is not the same as the test file we want to collect:
D:\code\pytest_quick\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.10s ===============================
然而,如果我们省去init.py文件,它就不会工作。这里是同一个目录,没有init.py文件。
错误信息强调我们有两个名字相同的文件,并建议改变文件名。改变文件名可以避免这个错误,但你也可以添加init.py文件,让它们保持原样。
当你遇到重复的文件名时,是一个如此令人困惑的错误,所以把init.py文件放在子目录中就可以了,这是一个不错的习惯。