Skip to content

Configuration

第6章:配置

到目前为止,已经顺便讨论了影响pytest的各种非测试文件。在本章中,我们将学习影响pytest的配置文件,研究这些文件的是如何改变 pytest的行为的,并对Tasks 项目的配置文件做一些更改。

6.1 配置文件

在讨论如何改变pytest的默认行为之前,让我们来看看 pytest 中的所有的非测试文件,特别是谁应该关心它们。

pytest.ini
这是pytest的主要配置文件,可以改变pytest默认行为。本章的大部分是讲述关于在pytest.ini 中可以修改的配置。

conftest.py
conftest.py模块是一个本地插件,允许为conftest.py文件所在的目录和所有子目录提供钩子函数和夹具的地方。在第5章我们已经学过。

__init__.py:
当放入每个测试子目录时,这个文件允许你在多个测试目录中拥有相同的名字的测试文件。

,你会对以下内容感兴趣:

  • tox.ini

如果你使用tox的话。tox.ini文件类似于 pytest.ini,但是用于 tox。当然,你可以把 pytest 配置放在这里,避免同时使用tox.ini和pytest.ini文件,保存一个配置文件即可。

更多关于tox的内容详见【第7章:配套工具】

setup.cfg
这是一个ini 文件样式,影响 setup.py 的行为的文件。可以在 setup.py 中添加几行代码来运行 python setup.py test 并让它运行所有的 pytest 测试。如果需要打包,可能已经有了一个setup.cfg 文件,你可以使用这个文件来存储 pytest 配置。

无论你将pytest 配置放在哪种文件中,大部分的格式都是相同的。

💡 在ch6目录中添加新目录format,添加新的配置文件pytest.ini

[pytest]
addopts = -rsxX -l --tb=short --strict
xfail_strict = true
... more options ...

💡 在ch6目录中添加新目录format,添加新的配置文件tox.ini

... tox specific stuff ...
[pytest]
addopts = -rsxX -l --tb=short --strict
xfail_strict = true
... more options ...

💡 在ch6目录中添加新目录format,添加新的配置文件setup.cfg

... packaging specific stuff ...
[tool:pytest]
addopts = -rsxX -l --tb=short --strict
xfail_strict = true
... more options ...
唯一的区别是setup.cfg文件的开头是[tool:pytest]而不是[pytest]

查看配置项列表

可以通过--help选项,获得pytest.ini所支持的所有有效的配置项列表。

执行命令:

$ pytest --help
输出结果:
...省略一万字
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file fou
nd:

  markers (linelist):   markers for test functions
  empty_parameter_set_mark (string):
                        default marker for empty parametersets
  norecursedirs (args): directory patterns to avoid for recursion
  testpaths (args):     directories to search for tests when no files
                        or directories are given in the command line.
  filterwarnings (linelist):
                        Each line specifies a pattern for
                        warnings.filterwarnings. Processed after
                        -W/--pythonwarnings.
  usefixtures (args):   list of default fixtures to be used with this
                        project
  python_files (args):  glob-style file patterns for Python test module

                        discovery
  python_classes (args):
                        prefixes or glob names for Python test class
                        discovery
  python_functions (args):
                        prefixes or glob names for Python test function

                        and method discovery
  disable_test_id_escaping_and_forfeit_all_rights_to_community_support
(bool):
                        disable string escape non-ascii characters,
                        might cause unwanted side effects(use at your
                        own risk)
  console_output_style (string):
                        console output: "classic", or with additional
                        progress information ("progress" (percentage) |

                        "count").
  xfail_strict (bool):  default for the strict parameter of xfail
                        markers when not given explicitly (default:
                        False)
  enable_assertion_pass_hook (bool):
                        Enables the pytest_assertion_pass hook.Make
                        sure to delete any previously generated pyc
                        cache files.
  junit_suite_name (string):
                        Test suite name for JUnit report
  junit_logging (string):
                        Write captured log messages to JUnit report:
                        one of no|log|system-out|system-err|out-err|all

  junit_log_passing_tests (bool):
                        Capture log information for passing tests to
                        JUnit report:
  junit_duration_report (string):
                        Duration time to report: one of total|call
  junit_family (string):
                        Emit XML for schema: one of
                        legacy|xunit1|xunit2
  doctest_optionflags (args):
                        option flags for doctests
  doctest_encoding (string):
                        encoding used for doctest files
  cache_dir (string):   cache directory path.
  log_level (string):   default value for --log-level
  log_format (string):  default value for --log-format
  log_date_format (string):
                        default value for --log-date-format
  log_cli (bool):       enable log display during test run (also known
                        as "live logging").
  log_cli_level (string):
                        default value for --log-cli-level
  log_cli_format (string):
                        default value for --log-cli-format
  log_cli_date_format (string):
                        default value for --log-cli-date-format
  log_file (string):    default value for --log-file
  log_file_level (string):
                        default value for --log-file-level
  log_file_format (string):
                        default value for --log-file-format
  log_file_date_format (string):
                        default value for --log-file-date-format
  log_auto_indent (string):
                        default value for --log-auto-indent
  pythonpath (paths):   Add paths to sys.path
  faulthandler_timeout (string):
                        Dump the traceback of all threads if a test
                        takes more than TIMEOUT seconds to finish.
  addopts (args):       extra command line options
  minversion (string):  minimally required pytest version
  required_plugins (args):
                        plugins that must be present for pytest to run

environment variables:
  PYTEST_ADDOPTS           extra command line options
  PYTEST_PLUGINS           comma-separated plugins to load during start
up
  PYTEST_DISABLE_PLUGIN_AUTOLOAD set to disable plugin auto-loading
  PYTEST_DEBUG             set to enable debug tracing of pytest's inte
rnals


to see available markers type: pytest --markers
to see available fixtures type: pytest --fixtures
(shown according to specified file_or_dir or current dir if not specifi
ed; fixtures with leading '_' are only shown with the '-v' option
输出的结果内容非常多,可以看到[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:这句之后的,展示的是pytest.ini文件中所有的可用的选项。

在本章中将讲述除doctest_optionflags外的配置项,doctest_optionflags将在第七章中介绍。

6.2 定制配置项

前面的设置列表不是固定不变的。插件和conftest.py都可以定制ini文件选项。通过定制添加的选项也会被添加到pytest --help的输出信息中。

addopts

添加命令选项

到目前为止,我们已经使用过了很多的pytest命令行选项,比如-v详细输出。我们会经常在一个项目中频繁使用这些选项。如果不想每次执行命令的时候都重复输入该选项,那么就可以在pytest.ini配置文件中,使用addopts添加自己想要的选项,这样就不必在每次执行的时候动手输入它们了。

💡 在ch6目录中添加新目录tests,然后将ch1中的两个测试测试文件test_one.pytest_two.py复制粘贴进来。

常规执行命令:

$ cd ch6/tests
$ pytest
输出结果:
======================== test session starts =========================
platform win32 -- Python 3.8.8, pytest-7.1.1, pluggy-1.0.0
rootdir: D:\Coding\Gitees\studypytest\ch6\tests, configfile: pytest.ini

plugins: nice-0.1.0
collected 2 items                                                     

test_one.py .                                                   [ 50%]
test_two.py F                                                   [100%]

============================== FAILURES ==============================
____________________________ test_failing ____________________________

    def test_failing():
>       assert (1, 2, 3) == (3, 2, 1)
E       assert (1, 2, 3) == (3, 2, 1)
E         At index 0 diff: 1 != 3
E         Use -v to get more diff

test_two.py:2: AssertionError
====================== short test summary info =======================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
==================== 1 failed, 1 passed in 0.10s =====================

如果不需要展示报错信息,并展示详细结果,常规做法是,执行时添加--tb=no-s选项。

$ cd ch6/tests
$ pytest --tb=no -v
--tb=no表示屏蔽报错信息;-v表示展示详细结果信息。

输出结果:

======================== test session starts =========================
platform win32 -- Python 3.8.8, pytest-7.1.1, pluggy-1.0.0 -- d:\system
\envs\mytasks\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Coding\Gitees\studypytest\ch6\tests, configfile: pytest.ini

plugins: nice-0.1.0
collected 2 items                                                     

test_one.py::test_passing PASSED                                [ 50%]
test_two.py::test_failing FAILED                                [100%]

====================== short test summary info =======================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
==================== 1 failed, 1 passed in 0.03s =====================

也可以通过添加pytest.ini配置文件配置执行命令时需要添加的选项,做到一劳永逸。

💡 在ch6/tests目录中添加新的配置文件pytest.ini

[pytest]
addopts = --tb=no -v

再次执行时,就不需要添加额外的参数了:

$ cd ch6/tests
$ pytest
输出结果:
======================== test session starts =========================
platform win32 -- Python 3.8.8, pytest-7.1.1, pluggy-1.0.0 -- d:\system
\envs\mytasks\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Coding\Gitees\studypytest\ch6\tests, configfile: pytest.ini

plugins: nice-0.1.0
collected 2 items                                                     

test_one.py::test_passing PASSED                                [ 50%]
test_two.py::test_failing FAILED                                [100%]

====================== short test summary info =======================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
==================== 1 failed, 1 passed in 0.03s =====================
可以看到,通过配置文件可以实现在命令行中手动输入一样的执行结果。

markers

注册标记

自定义标记可以用来筛选运行测试子集。但是,很容易拼错一个标记,比如小手一抖,容易搞出一些函数用@pytest.mark.smoke标记,而又有一些用@pytest.mark.somke标记的测试函数。问题是,pytest它也不会报错,而是认为这是想要创建两个不同的标记。如果由此产生莫名其妙的测试结果,通常会让人非常困惑,又很难排查问题。有个好办法避免这个隐患,就是通过在 pytest.ini 中注册标记来解决。

💡 在ch6/tests目录中添加新的目录markers_demo,在其中添加新的配置文件:

[pytest]
markers =
    smoke: Run the smoke test functions for tasks project
解释:冒号前面的是标记名称,冒号之后的是标记的描述信息。

目前

注册好标记之后,现在也可以通过pytest --markers 看到它们的描述信息。

macOS系统执行命令:

$ cd register
$ pytest --markers | grep smoke
windows系统执行命令:
$ pytest --markers | findstr smoke
输出结果:
@pytest.mark.smoke: Run the smoke test functions for tasks project
如果标记没有注册,它们就不会出现在pytest --markers结果列表中。

如果执行用例时使用了--strict-markers选项,任何带有拼写错误的标记,或未注册的标记都会被视为错误。

温馨提示:英文版原书中此处使用的是--strict选项是即将废弃的,官方推荐使用--strict-markers代替它。

💡 在ch6/tests/markers_demo目录中添加新的测试模块test_markers.py

import pytest


@pytest.mark.clifford
def test_markers():
    assert True
执行命令:
$ cd ch6/tests/markers_demo
$ pytest --strict-markers --tb=short
输出结果:
======================== test session starts =========================
platform win32 -- Python 3.8.8, pytest-7.1.1, pluggy-1.0.0
rootdir: D:\Coding\Gitees\studypytest\ch6\tests\markers_demo, configfil
e: pytest.ini
plugins: nice-0.1.0
collected 0 items / 1 error                                           

=============================== ERRORS ===============================
__________________ ERROR collecting test_markers.py __________________
'clifford' not found in `markers` configuration option
====================== short test summary info =======================
ERROR test_markers.py
!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!
========================== 1 error in 0.13s ==========================
可以看到,pytest使用严格标记模式运行测试,并且识别到在测试模块中使用了尚未注册的clifford标记,因此判定为错误。

要想解决修复这个错误,其实很简单,只需要在配置文件中,将clifford标记进行注册即可。

💡 修改ch6/tests/markers_demo目录中的配置文件pytest.ini,注册clifford标记:

[pytest]
markers =
    smoke: Run the smoke test functions for tasks project
    clifford: Run the test functions with clifford marker

执行命令:

$ cd ch6/tests/markers_demo
$ pytest --strict-markers
输出结果:
======================== test session starts =========================
platform win32 -- Python 3.8.8, pytest-7.1.1, pluggy-1.0.0
rootdir: D:\Coding\Gitees\studypytest\ch6\tests\markers_demo, configfil
e: pytest.ini
plugins: nice-0.1.0
collected 1 item                                                      

test_markers.py .                                               [100%]

========================= 1 passed in 0.02s ==========================

minversion

最低版本要求

minversion 允许你指定测试所需的最低的pytest 版本。

例如,approx()在测试浮点数是否足够接近时非常好用,但是这个特性直到 pytest3.0版本才被引入。

💡 在ch6/tests/minversion_demo目录中,添加新的测试模块test_float.py

from pytest import approx

def test_float_approx():
    assert 1.1 + 2.2 == approx(3.3)

为了避免误用,可以使用了approx()的项目中添加了配置信息。

[pytest]
minversion = 3.0
这样,使用了低于3.0版本的pytest尝试执行测试,就会出现一个错误消息提示。
ERROR: D:\Coding\Gitees\studypytest\ch6\tests\miniversion_demo\pytest.i
ni: 'minversion' requires pytest-9.0, actual pytest-2.1.1'

norecursedirs

测试目录黑名单

屏蔽某个目录,防止 pytest 找错地方。

recurse是指递归地遍历目录,那么norecurse就是不要递归遍历目录的意思。对于 pytest 来说,测试发现过程默认会递归地遍历许多目录来收集符合规则的测试函数。但是项目中总会有一些目录明显不需要pytest去递归查找用例的,比如说源码目录src,根本没有任何测试代码。

norecurse 的默认设置是'.* build dist CVS _darcs {arch} and *.egg。虚拟环境通常命名为.venv,所有以点号开头的目录都不会被遍历。然而,我有一个习惯,把它命名为venv,所以我可以把它添加到 norecursedirs 中。

对于Tasks 项目,你也可以在其中添加 src目录,因为让 pytest 在非测试代码中查找测试文件只会浪费时间。

[pytest]
norecursedirs = .* venv src *.egg dist build

当重写一个已经存在合法值的设置项时,最好知道默认值是什么,然后把你关心的设置放回去,就像我在前面的代码中用*.egg dist build的那样。

💡 在ch6/tests/目录中,添加新的目录norecursedirs_demo,然后创建新目录one,添加一个新的测试模块test_one.py:

# ch6/tests/norecursedirs_demo/one/test_one.py

def test_passing():
    assert (1, 2, 3) == (1, 2, 3)
💡 在ch6/tests/norecursedirs_demo目录中,添加新的目录two,添加一个新的测试模块test_two.py:
# ch6/tests/norecursedirs_demo/one/test_two.py

def test_failing():
    assert (1, 2, 3) == (3, 2, 1)

执行收集用例的命令:

$ cd ch6/tests/norecursedirs_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/norecursedirs_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 2 items                                                          

<Module one/test_one.py>
  <Function test_passing>
<Module two/test_two.py>
  <Function test_failing>

======================== 2 tests collected in 0.01s ========================
可以看到,目前是收集到了目录one和目录two中的测试模块,一共是2个测试模块。

💡 在ch6/tests/norecursedirs_demo目录中,添加新的配置文件pytest.ini

[pytest]
norecursedirs = one
再次执行用例收集命令:
$ cd ch6/tests/norecursedirs_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/norecursedirs_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

<Module two/test_two.py>
  <Function test_failing>

======================== 1 test collected in 0.01s =========================
可以看到,使用了norecursedirs = one选项之后,在用例发现的时候成功屏蔽了目录one,不会进入目录one中寻找测试文件。

testpaths

Specifying Test Directory Locations
测试目录白名单

相对于 norecursedirs 告诉 pytest 不要查看哪里,testpaths 则告诉 pytest 要查看哪里。

testspaths 是一个相对于根目录的目录列表,用于查找测试用例。只有当运行pytest时未指定任何的目录、文件或nodeid 参数时才会生效。

💡 在ch6/tests/目录中,添加新的目录testpaths_demo,然后创建新目录one,添加一个新的测试模块test_one.py:

# ch6/tests/testpaths_demo/one/test_one.py

def test_passing():
    assert (1, 2, 3) == (1, 2, 3)
💡 在ch6/tests/norecursedirs_demo目录中,添加新的目录two,添加一个新的测试模块test_two.py:
# ch6/tests/testpaths_demo/one/test_two.py

def test_failing():
    assert (1, 2, 3) == (3, 2, 1)

执行收集用例的命令:

$ cd ch6/tests/testpaths_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/testpaths_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 2 items                                                          

<Module one/test_one.py>
  <Function test_passing>
<Module two/test_two.py>
  <Function test_failing>

======================== 2 tests collected in 0.02s ========================
可以看到,目前是收集到了目录one和目录two中的测试模块,一共是2个测试模块。

💡 在ch6/tests/testpaths_demo目录中,添加新的配置文件pytest.ini

[pytest]
testpaths = one
执行用例收集命令:
$ cd ch6/tests/testpaths_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/testpaths_demo, configfile: pytest.ini, testpaths: one
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

<Module one/test_one.py>
  <Function test_passing>

======================== 1 test collected in 0.01s =========================
可以看到,使用了testpaths = one选项之后,在用例发现的时候只会锁定目录one,不会进入目录one之外的目录去寻找测试文件。

不过,这种方式的问题在于,如果经常在测试开发和调试期间在测试目录之间跳来跳去,可以很容易地测试某个子目录或文件,而不需要输出整个路径。因此,这个设置对交互式测试来说可能派不上用场。

然而,它对于从持续集成服务器,或者 tox 运行的测试非常有用。在这些情况下,根目录是固定的,你可以列出相对于根目录的相对目录。如果你确实需要压缩你的测试时间,所以减少一点测试发现的耗时是非常棒的。

乍一看,在配置文件中同时使用测试路径 testpathsnorecursedirs 似乎很愚蠢。然而,正如你所看到的,testpaths 对于来自文件系统不同部分的交互式测试帮助不大。在这种情况下,norecursedirs 可以提供帮助。另外,如果在tests目录中存在不包含测试文件的目录,你可以使用 norecursedirs 来避免测试这些奇葩的目录。话说回头,在tests目录中添加一些不含测试用例的目录有什么意义呢?

6.3 定制发现规则

Changing Test Discovery Rules

pytest 根据特定的测试发现规则查找需要运行的测试用例。

标准的测试发现规则是:

  • 从一个或多个目录开始。

你可以在命令行中指定文件名或目录名。如果不指定任何内容,则使用当前工作目录。

  • 在目录和所有子目录中递归查找测试模块

测试模块是以test_开头或 _test结尾的py文件。

  • 在测试模块中寻找以test_开头的测试函数。
  • 在测试模块中寻找以Test开头但不包含__init__方法的测试类,在类中寻找以test_开头的测试方法。

这些是标准的发现规则,当然,我们可以更改这些规则。

python_files

默认的测试模块的发现规则,即查找以test_*开头或以 *_test结尾的py文件。python_files 配置项则可以修改这个规则。

假设有一个自定义测试框架,在该框架中所有测试文件都是像check_xxx.py这样命名的。只需要在pytest.ini 中添加这样一行,就能避免重命名所有的测试文件。

💡 在ch6/tests目录中,添加新的目录pyton_files_demo,添加一个新的测试模块check_demo.py

# ch6/tests/python_files_demo/check_demo.py

def test_files_rule():
    assert True
执行用例收集命令:
$ cd ch6/tests/python_files_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/python_files_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 0 items                                                          

======================= no tests collected in 0.01s ========================
可以看到,collected 0 items,按照默认规则,未收集到任何测试文件。

💡 在ch6/tests/python_files_demo目录中,添加新的配置文件pytest.ini

[pytest]
python_files = check_*
再次执行用例收集命令:
$ cd ch6/tests/python_files_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/python_files_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

<Module check_demo.py>
  <Function test_files_rule>

======================== 1 test collected in 0.01s =========================
可以看到,已经成功地收集到了以check_开头的测试模块check_demo.py

python_classes

对于pytest 和 class类而言,通常的测试发现规则是,如果一个类以 Test开头,那么它就是一个潜在的测试类,并且这个类不能包含__init__()函数。

但是,如果偏偏就不按套路出牌,想把测试类进行非主流命名,比如以xxxTest结尾,或以 xxxSuite 结果,该怎么办呢?这就是 python_classes配置项的用武之地。

💡 在ch6/tests目录中,添加新的目录pyton_classes_demo,添加一个新的测试模块test_classes_demo.py

# ch6/tests/pyton_classes_demo/test_classes_demo.py

class DemoSuite:

    def test_classes_rule(self):
        self.flag = True
        assert self.flag is True
执行用例收集命令:
$ cd ch6/tests/python_classes_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/pyton_classes_demo
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 0 items                                                          

======================= no tests collected in 0.01s ========================
根据默认的测试类的收集规则,以Suite结尾的测试类中的测试方法并不会被收集。

💡 在ch6/tests/python_classes_demo目录中,添加新的配置文件pytest.ini

[pytest]
python_classes = *Suite
再次执行用例收集命令:
$ cd ch6/tests/python_classes_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/pyton_classes_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

<Module test_classes_demo.py>
  <Class DemoSuite>
      <Function test_classes_rule>

======================== 1 test collected in 0.01s =========================
可以看到,已经成功地收集到了以*Suite结尾的测试类DemoSuite中的测试方法。

python_functions

python_functions 配置项作用于测试函数和测试方法名称收集规则。默认的测试函数和测试方法的收集规则是test _*

💡 在ch6/tests目录中,添加新的目录pyton_functions_demo,添加一个新的测试模块test_functions_demo.py

# ch6/tests/pyton_functions_demo/test_functions_demo.py


def check_function():
    assert True

class TestFunctionsRule:

    def check_method(self):
        assert True
执行用例收集命令:
$ cd ch6/tests/python_functions_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/pytest_functions_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 0 items                                                          

======================= no tests collected in 0.01s ========================
根据默认的测试类的收集规则,以check_开头的测试函数和测试方法并不会被收集。

如果需要支持 check_*的函数命名则需按照下面这样添加配置。

💡 在ch6/tests/python_functions_demo目录中,添加新的配置文件pytest.ini

[pytest]
python_functions = check_*
再次执行用例收集命令:
$ cd ch6/tests/python_functions_demo
$ pytest --co
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/pytest_functions_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 2 items                                                          

<Module test_functions_demo.py>
  <Function check_function>
  <Class TestFunctionsRule>
      <Function check_method>

======================== 2 tests collected in 0.01s ========================
可以看到,已经成功地收集到了以check_开头的测试函数check_function和测试方法check_method

现在看来,pytest 命名规范看起来没有那么死板。如果你不喜欢默认的命名规范,那就改变命名规范。比如,迁移数或者重命名成百上千的现成测试文件绝对是一个很轻松的活儿。

6.4 禁用 XPASS

xfail_strict

设置 xfail_strict=true 会导致,当标记为 @pytest.mark.xfail的测试函数执行失败后不会被报告为错误。通常建议始终添加这个配置项。

💡 在ch6/tests目录中,添加新的目录xfail_strict_demo,添加一个新的测试模块test_demo.py

# ch6/tests/xfail_strict_demo/test_demo.py

import pytest


# 预期失败,实际执行成功 XPASS
@pytest.mark.xfail(reason="预期失败,实际成功")
def test_exp_fail_but_passed():
    assert True
常规执行命令:
$ cd ch6/tests/xfail_strict_demo
$ pytest -v
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1 -- /Users/xiaofo/Envs/pytest/bin/python3
cachedir: .pytest_cache
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/xfail_strict_demo
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

test_demo.py::test_exp_fail_but_passed XPASS (预期失败,实际成功)     [100%]

============================ 1 xpassed in 0.01s ============================
可以看到,当一个xfail标记的预期会执行失败的测试函数,实际却执行成功时,测试结果展示的是XPASS。

💡 在ch6/tests/python_functions_demo目录中,添加新的配置文件pytest.ini

[pytest]
xfail_strict=True
再次执行测试:
$ cd ch6/tests/xfail_strict_demo
$ pytest -v
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1 -- /Users/xiaofo/Envs/pytest/bin/python3
cachedir: .pytest_cache
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/xfail_strict_demo, configfile: pytest.ini
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

test_demo.py::test_exp_fail_but_passed FAILED                        [100%]

================================= FAILURES =================================
_________________________ test_exp_fail_but_passed _________________________
[XPASS(strict)] 预期失败,实际成功
========================= short test summary info ==========================
FAILED test_demo.py::test_exp_fail_but_passed
============================ 1 failed in 0.01s =============================
可以看到,对于预期失败的标记xfail,如果实际结果不符合预期(实际执行却通过)则直接判定为测试失败。

6.5 避免文件名冲突

Avoiding Filename Collisions

在一个项目的每个测试子目录中都有一个__init__.py文件,可能会让人困惑。然而,有这些文件和有这些文件的区别是什么?如果在所有的测试子目录中有__init__.py文件,则可以在多个目录中使用同名的测试模块。如果没有__init__.py文件则不行。就是这么简单,这就是__init__.py文件的影响。

💡 在ch6/tests目录中,添加新的目录dups_demo/dups_before/a,在其中添加新的测试模块test_the_same.py

def test_a():
    pass
💡 在ch6/tests/dups_demo/dups_before目录中,添加一个新的目录b,在其中添加新的测试模块test_the_same.py`:
def test_b():
    pass

当前dups_before目录结构如下:

dups_before
  ├── a
  │     └── test_same_name.py
  └── b
      └── test_same_name.py
可以看到,目录 a 和目录 b 都有一个test_same_name.py测试模块。这个模块中包含什么测试函数其实不重要。

单独指定运行目录a或目录b是可以的。
执行命令:

$ cd ch6/dups_demo/dups_before
$ pytest a
输出:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/dups_demo/dups_before
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

a/test_same_name.py .                                                [100%]

============================ 1 passed in 0.01s =============================

执行命令:

$ cd ch6/dups_demo/dups_before
$ pytest b
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/dups_demo/dups_before
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item                                                           

b/test_same_name.py .                                                [100%]

============================ 1 passed in 0.01s =============================

但是从dups_before目录直接运行 pytest 则不可以。

执行命令:

$ cd ch6/dups_demo/dups_before
$ pytest
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/dups_demo/dups_before
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 1 item / 1 error                                                 

================================== ERRORS ==================================
___________________ ERROR collecting b/test_same_name.py ___________________
import file mismatch:
imported module 'test_same_name' has this __file__ attribute:
  /Users/xiaofo/coding/Personal/studypytest/ch6/tests/dups_demo/dups_before/a/test_same_name.py
which is not the same as the test file we want to collect:
  /Users/xiaofo/coding/Personal/studypytest/ch6/tests/dups_demo/dups_before/b/test_same_name.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules
========================= short test summary info ==========================
ERROR b/test_same_name.py
!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!
============================= 1 error in 0.04s =============================
错误消息并没有真正说明出了什么问题。

要解决这个测试模块同名问题,只需要在子目录中添加添加空的__init__.py文件。

💡 在ch6/tests/dups_demo目录中,将dups_before目录整个复制一份并粘贴为dups_after,然后分别在它的字目录a和子目录b中添加空的__init__.py 文件,也就是说把目录a和目录b变成包package。

当前dups_after目录的结构:

dups_after
  ├── a
  │     ├── __init__.py
  │     └── test_foo.py
  └── b
      ├── __init__.py
      └── test_foo.py

进入dups_after执行命令:

$ cd ch6/dups_demo/dups_after
$ pytest
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch6/tests/dups_demo/dups_after
plugins: allure-pytest-2.9.45, nice-0.1.0
collected 2 items                                                          

a/test_same_name.py .                                                [ 50%]
b/test_same_name.py .                                                [100%]

============================ 2 passed in 0.01s =============================
OK,好多了。

也许在你的项目中永远不会存在同名的测试模块文件,但是没关系。随着项目的迭代,测试目录在增多,真的想等到这个模块同名问题出现再修复它吗?建议添加__init__.py文件,作为一种习惯,这样就一劳永逸了。


pytest 不仅本身非常强大,尤其是插件,而且它也能很好地与其他软件测试工具集成在一起。

在下一章中,你将看到 pytest 是如何与其他强大的测试工具的结合使用的。