Skip to content

Getting Started with pytest

第1章:启航

1.1 开胃小菜

测试通过的场景

💡 在项目中创建一个新包,名为ch1,在其中编写一个测试文件,名为 test_one.py

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

在终端中输入以下指令,执行测试用例:

$ cd ch1
$ pytest test_one.py
输出结果:
========================== 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/pythonProject/Pytest4Python/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                         

test_one.py .                                                      [100%]

=========================== 1 passed in 0.03s ============================
解释:输出结果中的第7行中,test_one.py后面的点号.表示运行的测试用例是通过的。
备注:终端命令的具体目录请参见输出结果中的rootdir信息。

如果你需要输出更多的信息,可以在执行命令中加上-v选项(等同于--verbose)。

$ cd ch1
$ pytest -v test_one.py
备注:选项的前后顺序不影响执行结果,比如pytest -v test_one.pypytest test_one.py -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/pythonProject/Pytest4Python/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                         

test_one.py::test_passing PASSED                                   [100%]

=========================== 1 passed in 0.03s ============================
如果你终端支持彩色显示,第8行中的PASSED[100%]和分割线===将显示成绿色的。

测试失败的场景

以下是一个失败的测试用例 test_two.py

💡 在ch1目录中,添加新的测试模块 test_one.py

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

让我们在终端中输入以下指令,来运行这个失败用例看看:

$ cd ch1
$ pytest test_two.py
输出结果:
========================== 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/pythonProject/Pytest4Python/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                         

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 the full diff

test_two.py:5: AssertionError
======================== short test summary info =========================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
=========================== 1 failed in 0.04s ============================
解释:失败的测试用例test_failing()有专门的信息告诉我们失败的原因。pytest 准确地告诉我们第一个失败原因是什么,输出结果第15行:E At index 0 diff: 1 != 3。意思是索引0处的值是不匹配的。

如果你的命令行终端支持彩色显示,那么异常结果的大部分都是红色显示的,这样显得错误更加突出。

我们还可以使用加上 -v选项再次执行,以便获得详细的报错信息。

$ cd ch1
$ pytest -v test_two.py
输出结果:
========================== 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/pythonProject/Pytest4Python/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                         

test_two.py::test_failing FAILED                                   [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         Full diff:
E         - (3, 2, 1)
E         ?  ^     ^
E         + (1, 2, 3)
E         ?  ^     ^

test_two.py:5: AssertionError
======================== short test summary info =========================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
=========================== 1 failed in 0.04s ============================
从中不难看出,pytest 添加了一些插入符号(^)来告诉我们到底有什么不同。

如果你已经对使用pytest 编写、搜索和运行测试用例的简单程度,以及读取输出来查看测试失败信息的容易程度印象深刻的话,其实,你还没有看到真正酷炫的东西,还有很多其他的有意思的内容。

别走开,让我告诉你为什么我认为 pytest 是绝对的最好的测试框架。

在本章的其余部分,你将学会如何安装pytest、查看它的不同运行方式,并运行一些最常用的命令行选项。在后续的章节中,你将学习如何充分发挥pytest的强大能量,来编写功能测试函数、使用前置setup和后置teardown,以及使用装置和插件来强化你的测试。

但首先,我要向你道歉,断言(1,2,3) == (3,2,1)这种确实太无聊了。没有人会在现实生活中写这样的测试代码。软件测试是由测试其他软件的代码组成的,而这些代码并不总是能够正常工作。并且(1,2,3) == (3,2,1)总是通过的。这就是为什么我们不会在本书的其余部分使用这样过于愚蠢的测试断言。

我们将看到一个真正的软件项目的测试,将使用一个需要一些测试代码的项目叫做 Tasks。希望它足够简单,容易理解,但又不至于简单到令人厌烦。

软件测试的另一个重要用途是测试你对被测软件的工作方式的假设,其中包括测试你对第三方模块和包的理解,甚至是内置的Python 数据结构。Tasks 项目使用一个叫做 Task 的结构,它是基于标准库中的“具名元组”(namedtuple)的工厂方法。Task 结构是被用作在 UI 界面和 API 接口之间传递信息的数据结构。在本章的其余部分,我将使用 Task 来演示如何运行 pytest ,以及使用一些常用的命令行选项。

Task 对象大概的结构是:

from collections import namedtuple

Task = namedtuple('Task', ['summary', 'owner', 'done', 'id'])

这个namedtuple()工厂方法自python2.6以来就存在了,但仍然有许多 Python 开发人员不知道它有多酷。至少,在测试示例中使用 Task将比(1,2,3)==(1,2,3)add(1,2)==3更有意思。

在我们进入示例之前,让我们先来讨论如何安装 pytest。

1.2 安装 pytest

pytest 的官方文档地址是:

https://docs.pytest.org/en/7.1.x/

它是通过PyPI(Python Package Index) 发布的。

https://pypi.org/project/pytest/

像其他通过PyPI 发布的 Python 软件包一样,推荐使用 pip 工具将 pytest 安装到专门用于测试的虚拟环境venv中。

通过pip安装

安装虚拟环境的第三方依赖库virtualenv的方法:

$ pip3 install -U virtualenv

创建一个名为venv的虚拟环境:

$ python3 -m virtualenv venv

激活这个venv的虚拟环境:

$ source venv/bin/activate

安装pytest库:

$ pip install pytest

1.3 执行pytest

如果想要查看pytest命令的用法,可以加上-v--help选项:

$ pytest --help
这将会打印出pytest命令所支持的所有的参数及用法说明。

运行全量

运行全量是指,在终端中只输入pytest,不指定任何目录或文件,也不加任何任何选项直接执行。

执行命令:

$ pytest
那么,pytest 将在当前所在目录及子目录中,搜索符合命名规范的测试文件,并自动执行它们。

如果你为 pytest 提供一个文件名,或一个目录名,或者这些文件名称的列表,那么它将查看对应的目录或文件,而不是只看当前的工作目录。命令行中列出的每个目录都会被递归地遍历以便查找测试代码。

让我们在ch1目录中,创建一个名为 tasks 的子目录。

$ cd ch1
$ makdir tasks

💡 在/ch1/tasks目录中,创建一个测试文件test_three.py

"""测试Task的数据类型"""

from collections import namedtuple

Task = namedtuple('Task', ['summary', 'owner', 'done', 'id'])
Task.__new__.__defaults__ = (None, None, False, None)


def test_defaults():
    """没有传参则会使用默认值"""
    t1 = Task()
    t2 = Task(None, None, False, None)
    assert t1 == t2


def test_member_access():
    """检查具名元组的 .field 功能"""
    t = Task('buy milk', 'brian')
    assert t.summary == 'buy milk'
    assert t.owner == 'brian'
    assert (t.done, t.id) == (False, None)
代码解释:

  • 可以调用.__new__.__defaults__来创建 Task 对象,不必指定所有的字段;
  • 测试函数test_defaults()是用来演示和验证默认值的;
  • 测试函数test_member_access()用于演示如何按名称而不是按索引访问成员,这是使用具名元组namedtuple的主要原因之一。

💡 在/ch1/tasks目录中,再新建一个名为test_four.py的文件。放入更多的测试用例,来演示_asdict()_replace()功能。

"""测试 Task 的数据类型"""

from collections import namedtuple

Task = namedtuple('Task', ['summary', 'owner', 'done', 'id'])
Task.__new__.__defaults__ = (None, None, False, None)


def test_asdict():
    """_asdict() 应当返回一个字典"""
    t_task = Task('do something', 'okken', True, 21)
    t_dict = t_task._asdict()
    expected = {
        'summary': 'do something',
        'owner': 'okken',
        'done': True,
        'id': 21
    }

    assert t_dict == expected


def test_replace():
    """_replace() 应当改变传入的字段"""
    t_before = Task('finish book', 'brian', False)
    t_after = t_before._replace(id=10, done=True)
    t_expected = Task('finish book', 'brian', True, 10)

    assert t_after == t_expected

当前的项目的目录结构:

.
└── ch1
    ├── tasks
    │   ├── test_four.py
    │   └── test_three.py
    ├── test_one.py
    └── test_two.py

运行目录

在终端中运行pytest命令的时候,你可以选择指定目录。

如果你没有指定任何文件或目录,pytest 则会自动在当前所在目录中查找测试目录和子目录,并且会查找以test _ 开头或以 test _ 结尾的py文件。

如果在 ch1目录中,只执行 pytest 命令不带任何选项,那么将会发现并运行4个测试文件。

执行命令:

$ pytest tasks
输出结果:
=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 4 items                                                          

tasks/test_four.py ..                                                [ 50%]
tasks/test_three.py ..                                               [100%]

============================ 4 passed in 0.03s =============================

运行模块

在终端中运行pytest命令的时候,你可以选择指定文件。

如果不想运行全部用例文件,而是只想运行其中部分测试文件,有两种办法可以实现。办法一是:在pytest 命令后加上所有你想要运行的文件名或者目录名。办法二是:切换到目标测试文件所在的目录,然后再执行 pytest 命令。

挑选执行/ch1/task目录中的test_three.pytest_four.py文件的命令如下:

$ pytest tasks/test_three.py tasks/test_four.py
输出结果:
========================== 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/pythonProject/Pytest4Python/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 4 items                                                        

tasks/test_three.py ..                                             [ 50%]
tasks/test_four.py ..                                              [100%]

=========================== 4 passed in 0.03s ============================

进入tasks目录,执行pytest命令:

$ cd ch1/tasks
$ 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/pythonProject/Pytest4Python/ch1/tasks
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 4 items                                                        

test_four.py ..                                                    [ 50%]
test_three.py ..                                                   [100%]

=========================== 4 passed in 0.03s ============================

运行函数

本文中出现的「测试用例」和「测试函数」术语是同一个概念。

日常编写测试函数的时候,经常会需要只想单独运行某一个刚写好的测试函数,很简单,只需要在pytest后面指定文件路径,然后接两个英文冒号::,然后再接上测试函数的名称。

执行命令:

$ cd ch1
$ pytest tasks/test_four.py::test_asdict
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                           

tasks/test_four.py .                                                 [100%]

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

1.4 用例发现

test discovery

测试发现是指:pytest 启动并查找所要运行的测试文件的过程。

只要我们遵循 pytest 「命名规范」来给那些想要运行的测试文件进行命名,那么pytest 就能够找到它们。

命名规范

naming conventions

下面是一个关于命名约定的简要概述,以保证你的测试代码可以被 pytest 发现:

  1. 测试文件:名称应该以test_开头,或者以_test结尾。

例如,命名为test_xxx.pyxxx_test.py

  1. 测试类:名称应该以Test开头。

例如,命名为 class TestXXX

  1. 测试函数:名称应该以 test_开头。

例如,命名为 def test_xxx()

如果你已经有一堆命名五花八门的测试文件,也不用担心,其实是有办法可以改变默认的用例发现规则的,详见本书第6章的相关内容。

1.5 结果详解

测试通过的结果

让我们仔细研究一下,只运行一个测试文件test_three.py的输出内容。

运行命令:

$ cd ch1/tasks
$ pytest test_three.py
输出结果:
========================== test session starts ===========================
platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1
rootdir: /pythonProject/Pytest4Python/ch1/tasks, configfile: pytest.ini
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 2 items                                                        

test_three.py ..                                                   [100%]

=========================== 2 passed in 0.03s ============================
输出结果有很多信息,我们逐一来分析。

第1行:===== test session starts =====

pytest 为测试会话(session)的开始提供了一个分隔符。会话包括在多个目录上运行的所有测试。本书后续章节讨论与 pytest 装置相关的会话的作用域时,会话的定义变得很重要。

第2行:platform darwin -- Python 3.9.7, pytest-6.2.0, py-1.10.0, pluggy-0.13.1

其中的platform darwin表示的运行平台是macOS系统,platform win32表示当前运行的平台是Windows系统。

--后面列出的是当前运行的 Python 版本号 和 pytest 版本号,还有 pytest 所依赖的两个包及对应的版本号信息。

py是包含跨python解析、ini文件解析和日志工具的包;pluggy 是 pytest 团队开发的用来帮助实现 pytest 的软件包。

第3~4行:rootdir: /pythonProject/Pytest4Python/ch1/tasks, configfile: pytest.ini

其中的rootdir 表示的是测试发现所搜索过的所有目录,它们共同的顶层目录的路径。这里指定执行的是test_three.py文件,因此它的顶层目录是tasks目录。

需要说明的是,configfile: pytest.ini这段信息只有在ch1目录中创建了pytest.ini文件后,才会显示。为了演示configfile信息的效果,其实提前悄悄创建了一个,它是负责列出正在使用的配置文件。配置文件可以是 pytest.initox.inisetup.cfg。在本书第6章将会有关于配置文件的详细介绍。

第4行:plugins: allure-pytest-2.9.45

其中的plugins列出的是当前系统已经安装的pytest相关插件名称和版本信息。

第5行:collected 2 items

表示的是收集了两个测试用例/函数。

结果行:test_three.py ..

这一行信息中的test_three.py 表示被测文件。每个测试文件都有一行。后面的两个点号.表示有2个测试方法是通过的,每个测试函数或方法对应一个点号标记。

测试结果的其他情形,例如测试失败failures、错误errors、跳过skips、 xfail 和 xpass ,则分别用 FE、小写 s、小写x 和 大写X 表示。详见本章节中的“结果状态”部分内容。

如果你希望不只是想看到.号这么简略的符号,而是想要展示更详细的结果信息,可以在执行pytest命令时机上 -v--verbose 的选项。

第9行:====== 2 passed in 0.03s ======

表示的是测试通过的次数,以及整个测试会话所花费的时间。如果存在未通过或其他情形的测试,每个类别的数量也会在这里分别列出。

测试的结果是运行测试,或者查看结果的人理解测试运行中到底发生了什么情况的主要方式。在pytest 中,测试函数可能有几个不同的结果,而不仅仅是通过或者失败。

测试失败的结果

在ch1目录中的test_two.py文件是典型的执行失败的场景,运行它来观察完整的报错信息。

执行命令:

$ pytest test_two.py
输出结果:
=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                           

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 the full 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 in 0.05s =============================
以上是常规执行后所输出完整的报错信息。

第9行:======= FAILURES ======

报错信息版块。下方将展示所有的ERRORFAILED状态的失败函数信息。

第10~行:_ test_failing __

函数版块。展示的是测试失败的具体函数相关报错信息。

第12行:def test_failing():

代码行。展示的是执行失败的测试函数的相关代码。

第12行:> assert (1, 2, 3) == (3, 2, 1)

断言行。带有>符号标志,展示测试失败的函数中的断言语句。

第13~15行:E assert (1, 2, 3) == (3, 2, 1)

追踪行。带有E标志,展示的是断言失败的追踪信息。

第17行:D:\Coding\PythonRepo\PytestDemo\ch1\test_two.py:2: AssertionError

定位行。展示错误定位信息,由文件路径、错误行号和错误名称组成。展示的信息通过冒号分隔,第一段表示测试文件在磁盘上的完整路径,第二段的数字表示引发失败的代码位于测试文件中的行号,第三段展示的是失败的错误名称。

第18~20行:====== short test summary info ======

概要信息版块。展示的是简短的概要信息。

第19行:FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)

报错概要行。其中FAILED是结果状态,表示执行失败;test_two.py是模块名;::是模块名和函数名的分隔符,test_failing是函数名,-是信息分隔符,assert (1, 2, 3) == (3, 2, 1)是断言语句。

结果状态

以下是测试的六种可能出现的结果状态:

名称标志 符号标志 颜色 含义
PASSED . 绿色 执行通过
FAILED F 红色 执行失败
ERROR E 红色 运行错误(用例之外)
SKIPPED s 黄色 跳过执行
XFAIL x 黄色 期望失败,实际执行失败
XPASS X 黄色 期望失败,实际执行成功
  • PASSED(.

成功。简写为点号.,表示测试用例执行成功。

  • FAILED(F

失败。简写为大写字母F,表示测试用例执行未通过。

  • ERROR(E

错误,简写为E。表示在测试函数之外、在装置(fixture)或是钩子函数(hook function)之中发生了错误。关于装置的相关知识详见第三章,关于钩子函数的相关知识详见第五章。

  • SKIPPED(s

跳过。测试被跳过/略过了,也就是说实际不会执行。可以通过使用装饰器@pytest.mark.skip()或者@pytest.mark.skipif()来告诉 pytest 跳过某个测试函数,不要去执行它其中的代码。本书将在第二章中的“跳过测试”部分详细讨论。

  • XFAIL(x

预期失败,实际失败。xfail含义是expected to fail,简写为小写字母x。可以通过使用装饰器@pytest.mark.xfail()告诉 pytest 一个测试预期会失败,如果测试函数实际执行失败,则会认定为XFAIL。关于此装饰器,详见第二章中的“将测试标记为预期失败”部分内容。

  • XPASS(X

预期失败,实际成功。xpass含义是expected to fail but passed,简写为大写字母x。它也是通过使用装饰器@pytest.mark.xfail()来告诉 pytest 一个测试预期会失败,如果测试函数实际执行却是通过的,则会认定为XPASS。

示例代码:

import sys
import pytest


# 装置
@pytest.fixture
def fx():
    assert False


# 通过 PASSED
def test_passed():
    assert 1 == 1


# 失败 FAILED
def test_failed():
    assert False


# 错误 ERROR
def test_error(fx):
    pass


# 跳过 SKIPPED
@pytest.mark.skip(reason="skip unconditional")
def test_skipped_unconditional():
    pass


# 条件跳过 SKIPPED
@pytest.mark.skipif(sys.platform == "win32", reason="skip if on windows")
def test_skipped_if():
    pass


# 预期失败,实际失败 XFAIL
@pytest.mark.xfail(reason="预期失败,实际失败")
def test_exp_fail_and_fail():
    assert False


# 预期失败,实际成功 XPASS
@pytest.mark.xfail(reason="预期失败,实际成功")
def test_exp_fail_but_pass():
    assert True


# 注定失败 FAILED
@pytest.mark.xfail(strict=True)
def test_exp_to_fail_strict():
    assert True

如果查看简单的测试结果标志,可以执行命令:

$  pytest test_outputs.py --tb=no
备注:这里的--tb=no选项是为了屏蔽失败用例所打印的报错信息。

输出结果:

=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 8 items                                                          

test_outputs.py .FEs.xXF                                             [100%]

========================= short test summary info ==========================
FAILED test_outputs.py::test_failed - assert False
FAILED test_outputs.py::test_expect_to_fail_strict
ERROR test_outputs.py::test_error - assert False
== 2 failed, 2 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.01s ===
这里显示了.FEs.xXF简单标志。

如果查看详细的测试结果标志,执行命令:

$ pytest test_outputs.py -v --tb=no
备注:这里的-v选项是控制详细显示,--tb=no选项是为了屏蔽失败用例所打印的报错信息。

输出结果:

=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- /usr/local/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 8 items                                                          

test_outputs.py::test_passed PASSED                                  [ 12%]
test_outputs.py::test_failed FAILED                                  [ 25%]
test_outputs.py::test_error ERROR                                    [ 37%]
test_outputs.py::test_skipped SKIPPED (skip unconditional)           [ 50%]
test_outputs.py::test_skipped_if PASSED                              [ 62%]
test_outputs.py::test_exp_fail_and_failed XFAIL (预期失败,实际失败)     [ 75%]
test_outputs.py::test_exp_fail_but_passed XPASS (预期失败,实际成功)     [ 87%]
test_outputs.py::test_expect_to_fail_strict FAILED                   [100%]

========================= short test summary info ==========================
可以看到,.号显示成了PASSED,其他标志分别对应自己的详细名称。另外,这里SKIPPEDXFAILXPASS后面的括号中的信息,是该装饰器所指定的reason参数值。

1.6 使用选项

Using Options

我们已经使用了详细选项-v--verbose选项好几次了,因此有必要深入了解这些命令行选项。

下面介绍一些在使用 pytest 时非常有用的选项。它们能够满足我们平时控制 pytest 如何运行的一些基本需求,尤其是在刚开始起步的时候。

-h

-h--help选项的简写。可以通过在终端命令行中执行 pytest -h或者pytest --help 命令,来查看pytest帮助文档。它不仅会展示如何使用pytest的各类选项,而且当你安装插件后,它还会展示插件所支持的选项和配置。

执行命令:

$ pytest --help
输出结果:
usage: pytest [options] [file_or_dir] [file_or_dir] [...]

positional arguments:
  file_or_dir

general:
  -x, --exitfirst       exit instantly on first error or failed test.

......此处省略一万字(作者注)
(shown according to specified file_or_dir or current dir if not specified; fixtures with leading '_' are only shown with the '-v' option

由于输出结果非常长,这里仅挑选其中一个选项-x选项为例。

第1行:usage: pytest [options] [file_or_dir] [file_or_dir] [...]

介绍pytest命令行的用法,以pytest命令开头,然后选择性地添加选项options,多个文件或路径名。

第3~4行:positional arguments: file_or_dir

表示文件或路径名是位置参数,也就是说,如果添加了多个,那么存在顺序关系,依次执行。另外,选项options是非位置参数,因此写在文件或路径名前和后都可以。

第6行及以后:general:

列举了所有的参数和说明信息。

第7行:-x, --exitfirst exit instantly on first error or failed test.

其中逗号前面的-x是选项的简写形式,--exitfirst是选项的完整名称,空格之后的exit instantly on first error or failed test.是对这个选项的解释信息。

第10行:shown according to...

帮助文档的最后一点信息是:(shown according to specified file_or_dir or current dir if not specified)这句很重要,因为选项、标记和fixture装置可以根据运行的目录或测试文件发生变化。这是因为根据指定文件或目录的路径,pytest 可能会找到新的 conftest.py 配置文件,其中可能存在含有其他选项的hook钩子函数、 fixture装置和markers标记的定义语句。

关于配置文件conftest.py 和定制 pytest 行为的有关内容,详见第6章的内容。

-V

display pytest version and information about 展示pytest版本号


-V--version选项的简写,用来展示当前已安装的 pytest 的版本。注意简写时候是大写字母。

执行命令:

$ pytest --version
输出结果:
pytest 6.2.0

-v

increase verbosity. 展示更加丰富的结果信息。

-v--verbose选项的简写,展示更多的输出信息。

是否有使用-v选项的区别点:

普通执行 使用-v选项
python路径 显示
cachedir路径 显示
用例名称 模块名称 模块名称::函数名称
用例结果 符号标志(如. 名称标志(如PASSED

最明显的区别是,每个测试函数都有自己的结果行信息,并且函数和实际执行结果都是按名称展示的。

运行命令,不使用-v选项:

$ pytest test_one.py
输出结果:
================================= test session starts =================================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\Gitees\studypytest\ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                                       

test_one.py .                                                                    [100%]

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

运行命令,使用-v选项:

$ pytest test_one.py -v
输出结果:
================================= test session starts =================================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0 -- d:\programs\python3\python
.exe
cachedir: .pytest_cache
rootdir: D:\Coding\Gitees\studypytest\ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                                       

test_one.py::test_passing PASSED                                                 [100%]

================================== 1 passed in 0.02s ==================================
可以看到,测试文件后详细展示了测试函数的名称,并且执行通过的状态,已经显示成PASSED而不再是一个容易被看漏掉的小.了。

-s

disable all capturing.one of per-test capturing method, shortcut for --capture=no. 禁用捕获。它是捕获方法中的一种,--capture=no的专用简写

-s--capture=no选项的简写,用来禁用所有的捕获行为。

它允许在测试运行时将 print 语句(或者实际上任何通常会打印到 stdout流的输出)打印到 stdout

在编写测试过程中,有时添加几个print()语句很有用,这样我就可以观察测试的流程。输出信息也许能帮你搞清楚到底哪里出错了。

捕获机制
pytest默认的捕获机制是:在测试执行期间,会尝试捕获任何发到stdoutstderr的输出内容;至于stdin,默认被设成了空对象,因为运行自动化测试期间完全没有交互式输入的必要。

捕获默认是开启的,会拦截那些写入低级文件描述符的内容,因此测试函数的print()语句的输出和由测试用例所启动的子进程的输出都会被捕获。所以当捕获生效且用例运行通过的时候,不论在测试用例中有多少print语句,都不会在控制台上显示。

如果想看到被捕获的内容,有两种情形:一种是直接关闭捕获方法;另一种是,当测试函数或者前置方法执行失败了,在报错信息中可以看到对应的输出信息。

我们来举个例子,演示捕获行为。

ch1目录中,新建test_options.py文件,添加函数test_print_being_captured()

import sys


# print到stdout的输出会被捕获
def test_print_being_captured():
    print("关闭捕获后,我才会出现")
    sys.stdout.write("我是stdout")
    sys.stderr.write("我是stderr")
    assert True
当前的项目结构:
.
├── README.md
└── ch1
    ├── tasks
    │   ├── test_four.py
    │   └── test_three.py
    ├── test_one.py
    ├── test_options.py
    ├── test_outputs.py
    └── test_two.py

执行命令:

$ cd ch1
$ pytest test_options.py::test_print_being_captured
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                           

test_options.py .                                                    [100%]

============================ 1 passed in 0.01s =============================
因为这些stdoutstderr输出流都被py test捕获了,所以从输出结果中时看不到任何输出信息。

现在加上-s或者--capture=no选项关闭捕获行为。

运行命令:

$ cd ch1
$ pytest -s test_options.py::test_print_being_captured
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                           

test_options.py 关闭捕获后,我才会出现
我是stdout.

============================ 1 passed in 0.01s =============================
我是stderr%                                                                 
这时候,关闭捕获行为后,就可以顺利看到print语句,以及主动写入的stdoutstderr的输出信息了。

当捕获行为开启的时候,如果一个测试函数或者前置方法执行失败了,被捕获的信息会自动展示在报错信息(traceback)中。

test_options.py文件中,添加一个测试函数test_show_captured_when_fail()

# 失败时展示被捕获的输出信息
def test_show_captured_when_fail():
    print("失败时我就会出现")
    assert False
执行命令:
$ cd ch1
$ pytest test_options.py::test_show_captured_when_fail
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                           

test_options.py 失败时我就会出现
F

================================= FAILURES =================================
_______________________ test_show_captured_when_fail _______________________

    def test_show_captured_when_fail():
        print("失败时我就会出现")
>       assert False
E       assert False

test_options.py:13: AssertionError
========================= short test summary info ==========================
FAILED test_options.py::test_show_captured_when_fail - assert False
============================ 1 failed in 0.05s =============================
可以看到,虽然捕获行为是开启的,但是测试执行失败后,出现了Captured stdout call信息,在第19行展示了测试函数中的print的内容。

捕获方法
捕获方法--capture=method支持的所有参数有:fdsysnotee-sys

捕获选项 作用
--capture=no 禁用所有的捕获
--capture=sys 使用in-mem files替代sys.stdout/stderr
--capture=fd filedescriptors 1 and 2指向temp file
--capture=tee-sys 结合sysno,捕获并传入sys.stdout/stderr

备注:其中-s表示的--capture=no选项比较常用,其他选项不常见。

-r chars

show extra test summary info as specified by chars 展示概要信息

此选项可以根据给定的表示结果状态的chars字符,在输出结果中展示对应状态的概要信息。

支持的字符值有:

chars 全称 作用
-r w warnings 展示警告信息的概要,默认自带
-r f failed 展示failed失败结果的概要
E Error 展示Error错误结果的概要
s skipped 展示skipped跳过结果的概要
x xfailed 展示xfailed预期失败结果的概要
X Xpassed 展示Xpassed意外通过结果的概要
p passed 展示passed通过结果的概要
P Passed with output 展示带输出的passed结果的概要
a all except p/P 展示除passed和Passed with output之外的结果的概要
A All 展示全部结果的概要
N 重置参数列表

chars的默认值是 -r fE

-q

decrease verbosity. 展示更简洁的结果信息。

-q--quiet选项的简写。它用来精简输出信息,与 -v 选项刚好相反。

分别运行普通的pytest命令,以及带有-q选项的pytest命令,观察它们之间的区别。

不带-q选项执行pytest命令:

$ pytest test_one.py

输出结果:

================================= test session starts =================================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\Gitees\studypytest\ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                                       

test_one.py .                                                                    [100%]

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

带有-q选项的pytest命令:

$ pytest test_one.py -q
输出结果:
.                                                                                [100%]
1 passed in 0.00s
可以看到,输出的结果非常简洁。

通常可以把-q--tb=line组合使用,只展示测试不通过时的失败信息行。

-l

show locals in tracebacks (disabled by default). 在报错信息中展示局部变量

-l--showlocals的简写,作用是当测试失败时,将在报错信息中展示测试函数的局部变量。

ch1目录中的test_options.py文件中添加一个新的测试函数test_show_locals()

# 失败时展示局部变量
def test_show_locals():
    name = "Clifford"
    age = 20
    data = {"lan": "Python", "rank": 1}
    assert False

输入命令:

$ cd ch1
$ pytest test_options.py::test_show_locals -l
输出信息:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 1 item                                                           

test_options.py F                                                    [100%]

================================= FAILURES =================================
_____________________________ test_show_locals _____________________________

def test_show_locals():
name = "Clifford"
age = 20
data = {"lan": "Python", "rank": 1}
>       assert False
E       assert False

age        = 20
data       = {'lan': 'Python', 'rank': 1}
name       = 'Clifford'

test_options.py:23: AssertionError
========================= short test summary info ==========================
FAILED test_options.py::test_show_locals - assert False
============================ 1 failed in 0.05s =============================
可以看到,报错信息中出现了test_show_locals栏目的信息,并且展示了测试函数的本地变量agedataname的值,这对于调试来说很有帮助。

-k expr

only run tests which match the given substring expression. 根据关键字表达式匹配测试函数

此选项允许指定一个表达式EXPRESSION,来收集想要运行的那些测试函数,也就是挑选运行的一种方式。

这里的EXPRESSION支持python表达式的写法,由逻辑运算符(and,or,not)和关键字组成,用来匹配名称中包含子串且满足表达式的所有函数和类。表达式中的关键字必须使用单引号'或者双引号"包围,匹配名称时严格区分大小写。

表达式支持的语法:

关键字表达式 结果
-k 'abc' 匹配名称中含有abc的函数/类
-k 'not abc' 匹配名称中不含abc的函数/类
-k 'abc and xyz' 匹配名称中同时含有abc和xyz的函数/类
-k 'abc or xyz' 匹配名称中含有abc,或者含有xyz的函数/类
-k 'not abc and not xyz' 匹配名称中既不含abc也不含xyz的函数/类

如果某个测试函数的名称是唯一的,它也可以用作运行一个测试函数的快捷方式,或者运行名称中含有公共前缀或后缀的一组测试。

studypytest
      └── ch1
          ├── tasks
          │   ├── test_four.py
          │   └── test_three.py
          ├── test_one.py
          ├── test_outputs.py
          └── test_two.py

由于目前我们的项目模块中都是测试函数,没有编写测试类,所以先来看看匹配函数名称的表达式。

执行命令:

$ pytest --collect-only
输出结果:
=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 14 items                                                         

<Module test_one.py>
  <Function test_passing>
<Module test_outputs.py>
  <Function test_passed>
  <Function test_failed>
  <Function test_error>
  <Function test_skipped>
  <Function test_skipped_if>
  <Function test_exp_fail_and_failed>
  <Function test_exp_fail_but_passed>
  <Function test_expect_to_fail_strict>
<Module test_two.py>
  <Function test_failing>
<Module tasks/test_four.py>
  <Function test_asdict>
  <Function test_replace>
<Module tasks/test_three.py>
  <Function test_defaults>
  <Function test_member_access>

======================= 14 tests collected in 0.02s ========================

假设,现在需要运行test_four.py模块中的test_asdict()test_three.py模块中的test_defaults()函数,那么保险起见,先使用-k配合 --collect-only 选项来预先收集用例,看看是否能选中目标测试函数。

执行命令:

$ cd ch1
$ pytest -k "asdict or defaults" --collect-only
输出结果:
=========================== test session starts ============================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\PythonRepo\PytestDemo\ch1
plugins: allure-pytest-2.9.45
collected 5 items / 3 deselected / 2 selected                               

<Module tasks/test_four.py>
  <Function test_asdict>
<Module tasks/test_three.py>
  <Function test_defaults>

=============== 2/5 tests collected (3 deselected) in 0.02s ================
匹配结果看起来就是我们想要的。

接下来,就可以大胆地移除--collect-only选项,来真正运行测试了。

执行命令:

$ cd ch1
$ pytest -k "asdict or defaults"
输出结果:
=========================== test session starts ============================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\PythonRepo\PytestDemo\ch1
plugins: allure-pytest-2.9.45
collected 5 items / 3 deselected / 2 selected                               

tasks\test_four.py .                                                  [ 50%]
tasks\test_three.py .                                                 [100%]

===================== 2 passed, 3 deselected in 0.03s ======================
OK,分别显示了两个.标志,表明这两个被选中的用例执行通过了。

-m expr

only run tests matching given mark expression. 匹配含有满足表达式的标记的用例

标记(markers)是获取测试用例子集的最好方法之一,通过标记表达式(MARKEXPR)进行匹配,这样匹配到的测试函数就可以一起运行了。

studypytest
      └── ch1
          ├── tasks
          │   ├── test_four.py
          │   └── test_three.py
          ├── test_one.py
          ├── test_outputs.py
          └── test_two.py
例如,要想一起运行test_four.py模块中的test_replace()test_three.py模块中的test_member_access(),有一个办法就是给它们打上标记,即便它们分散于各个不同的py文件中。

你可以使用任何喜欢的标记名称。假设你想使用run_these_please,那么你可以使用装饰器@pytest.mark.run_these_please 来给那两个测试函数打上标记。

首先,改造一下test_four.py模块中的test_replace()函数:

import pytest

# 打上标记
@pytest.mark.run_these_please
def test_replace():
    """_replace() should change passed in fields."""
    t_before = Task('finish book', 'brian', False)
    t_after = t_before._replace(id=10, done=True)
    t_expected = Task('finish book', 'brian', True, 10)

    assert t_after == t_expected
然后,改造 test_three.py模块中的test_member_access()函数:
import pytest

# 打上标记
@pytest.mark.run_these_please
def test_member_access():
    """Check .field functionality of namedtuple."""
    t = Task('buy milk', 'brian')
    assert t.summary == 'buy milk'
    assert t.owner == 'brian'
    assert (t.done, t.id) == (False, None)

执行命令:

$ cd ch1/tasks
$ pytest -v -m run_these_please
输出结果:
=========================== test session starts ============================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\PythonRepo\PytestDemo\ch1
plugins: allure-pytest-2.9.45
collected 5 items / 3 deselected / 2 selected                               

tasks\test_four.py .                                                  [ 50%]
tasks\test_three.py .                                                 [100%]

============================= warnings summary =============================
tasks\test_four.py:23
  D:\Coding\PythonRepo\PytestDemo\ch1\tasks\test_four.py:23: PytestUnknownMar
kWarning: Unknown pytest.mark.run_these_please - is this a typo?  You can reg
ister custom marks to avoid this warning - for details, see https://docs.pyte
st.org/en/stable/how-to/mark.html
    @pytest.mark.run_these_please

tasks\test_three.py:18
  D:\Coding\PythonRepo\PytestDemo\ch1\tasks\test_three.py:18: PytestUnknownMa
rkWarning: Unknown pytest.mark.run_these_please - is this a typo?  You can re
gister custom marks to avoid this warning - for details, see https://docs.pyt
est.org/en/stable/how-to/mark.html
    @pytest.mark.run_these_please

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=============== 2 passed, 3 deselected, 2 warnings in 0.03s ================
备注:warnings summary 信息无关紧要。至于出现的原因,是因为当前版本的pytest提倡先注册再使用标记。配置文件具体内容详见第六章中pytest.ini部分。

标记表达式(MARKEXPR)不一定是单个标记,可以使用逻辑运算符进行灵活匹配。注意引号的使用。

  • 对于匹配同时拥有mark1mark2两个标记的测试函数,可以写成-m "mark1 and mark2";
  • 对于匹配有mark1标记,但没有mark2 标记的函数,可以写成-m "mark1 and not mark2";
  • 对于匹配有mark1标记或者有mark2标记的函数,可以写-m "mark1 or mark2"
MARKEXPR 表达式 匹配效果
-m mark1 匹配拥有mark1标记的测试函数
-m "mark1 and mark2" 匹配同时拥有mark1mark2两个标记的测试函数
-m "mark1 and not mark2" 匹配有mark1标记,但没有mark2 标记的测试函数
-m "mark1 or mark2" 匹配有mark1或者有mark2标记的测试函数

备注:关于标记的用法,详见第二章中的“标记测试函数”部分内容。

-x

exit instantly on first error or failed test. 但凡遇到失败或错误就立即退出会话

-x选项是--exitfirst选项的简写,它的作用是,如果遇到执行失败则立刻结束整个会话,对测试失败零容忍。一旦失败,立刻罢工。

正常的情况下,pytest 的运行机制是执行完它发现的每个测试函数。当一个测试函数遇到一个失败的断言或抛出异常,该函数的执行就到此为止,然后 pytest 会继续运行下一个测试用例,直到所有用例执行完毕。大多数时候,这的确就是你想要的。

然而,特别是在用例调试的时候,也许我们更希望一种效果,就是一旦某个测试用例失败时,就立即停止整个测试会话。这就是-x 选项的用武之地,它对失败采取零容忍的态度,一旦遇到失败立刻罢工。

执行命令:

$ cd ch1
$ pytest -x
输出结果:
=========================== test session starts ============================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\PythonRepo\PytestDemo\ch1
plugins: allure-pytest-2.9.45
collected 13 items                                                          

test_outputs.py .F

================================= FAILURES =================================
_______________________________ test_failed ________________________________

    def test_failed():
>       assert False
E       assert False

test_outputs.py:18: AssertionError
========================= short test summary info ==========================
FAILED test_outputs.py::test_failed - assert False
!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!
======================= 1 failed, 1 passed in 0.11s ========================
结果分析:第5行的collected 13 items告诉我们,当前收集到了13个测试函数;第20行,1 failed, 1 passed in 0.11s表明有一个测试函数执行失败了,有一个测试函数测试执行通过了;第19行的stopping after 1 failures表明测试会话在遇到一个测试失败就立即停止了。

如果没有-x选项,所有的13个测试函数都会运行。我们可以使用 --tb=no 选项来关闭报错信息,让结果更清爽直观。

此时不带 -x 选项,使用--tb=no选项,执行命令:

$ cd ch1
$ pytest --tb=no
输出结果:
========================== test session starts ===========================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 13 items                                                       

test_outputs.py .FEs.xXF                                           [ 61%]
test_two.py F                                                      [ 69%]
tasks/test_four.py ..                                              [ 84%]
tasks/test_three.py ..                                             [100%]

======================== short test summary info =========================
FAILED test_outputs.py::test_failed - assert False
FAILED test_outputs.py::test_expected_to_fail_strict
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
ERROR test_outputs.py::test_error - assert False
= 3 failed, 6 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.01s ==
以上输出信息证明了,如果没有-x选项,pytest 会在 test_two.py 中注意到失败,但仍旧继续运行后续的测试函数。

--co

only collect tests, don't execute them. 只预先收集用例,不会实际执行。

--co--collect-only选项的简写。该选项将为你展示根据当前项目的配置文件和给定的选项,哪些测试函数将被收集并运行。它是一个预告行为,并不会实际运行测试用例。

执行如下指令,会看到截至目前为止,你在本章中看到的所有测试函数:

$ cd ch1
$ pytest --collect-only

输出结果:

=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 14 items                                                         

<Module test_one.py>
  <Function test_passing>
<Module test_outputs.py>
  <Function test_passed>
  <Function test_failed>
  <Function test_error>
  <Function test_skipped>
  <Function test_skipped_if>
  <Function test_exp_fail_and_failed>
  <Function test_exp_fail_but_passed>
  <Function test_expect_to_fail_strict>
<Module test_two.py>
  <Function test_failing>
<Module tasks/test_four.py>
  <Function test_asdict>
  <Function test_replace>
<Module tasks/test_three.py>
  <Function test_defaults>
  <Function test_member_access>

======================= 14 tests collected in 0.02s ========================

这个--collect-only选项有助于在真正运行测试用例之前,预先检查其他的用于过滤用例的选项是否正确。我们将在后面讲到-k 选项时配合使用,来进一步感受它的魅力。

--maxfail=num

exit after first num failures or errors. 当失败或错误的总次数达到阈值后立即结束会话。

此选项可以用来设置失败阈值,意味着可以容忍失败或错误,但是次数有限。

如果你不想做的像-x选项那么无情,而是可以容忍一些测试失败的存在,但又不能是无限度的,那么可以使用 --maxfail 选项来指定有多少次失败/错误是可以接受的。

到目前为止,我们的项目studypytest中的所有测试函数的执行结果是3 failed, 6 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error,也就是说一共有4个执行不通过的测试函数(3个failed 和 1个error)。如果我们在运行pytest时设置选项--maxfail=5,那么所有的测试都应该运行。

将失败阈值设定为5,执行命令:

$ pytest --maxfail=5 --tb=no
备注:--maxfail=5表示当失败/错误的次数达到5次则立即结束会话;--tb=no表示屏蔽繁冗的报错信息。

输出结果:

========================== test session starts ===========================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest/ch1
plugins: allure-pytest-2.9.45
collected 13 items                                                       

test_outputs.py .FEs.xXF                                           [ 61%]
test_two.py F                                                      [ 69%]
tasks/test_four.py ..                                              [ 84%]
tasks/test_three.py ..                                             [100%]

======================== short test summary info =========================
FAILED test_outputs.py::test_failed - assert False
FAILED test_outputs.py::test_expected_to_fail_strict
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
ERROR test_outputs.py::test_error - assert False
= 3 failed, 6 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.03s ==
备注:test_two.py后面的F说明已经执行失败了1次,当前设定的失败阈值是2次,没有超过限制,毕竟还有1次可以失败的机会。因此后续test_four.pytest_three.py会继续执行。

如果使用--maxfail=1选项运行,实际执行的结果将和 -x选项的结果一模一样。也可以理解为这两个写法其实是等价的。

执行命令:

$ cd ch1
$ pytest --maxfail=1 --tb=no
输出结果:
=========================== test session starts ============================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaofo/coding/Personal/studypytest
plugins: allure-pytest-2.9.45
collected 13 items                                                         

ch1/test_outputs.py .F

========================= short test summary info ==========================
FAILED ch1/test_outputs.py::test_failed - assert False
!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!
======================= 1 failed, 1 passed in 0.02s ========================
备注:第7行的ch1/test_outputs.py文件后面的.F说明先后执行了1个通过的用例和1个失败的用例。由于目前设定的失败阈值是1,意味着最多允许1次失败,因此达到上限,本次测试会话立即停止,并在结果中显示了stopping after 1 failures,并且后续的其他测试函数将不会执行。

--lf

rerun only the tests that failed at the last run (or all) 失败重跑

--lf--last-failed选项的简写,专门用来执行前一次执行结果是失败(FAILED)和错误(ERROR)的测试函数。

当遇到测试函数执行失败后,我们通常需要针对其中的未通过的用例进行调试,这时候--lf 就可以派上用场了。

首先,正常执行一次,观察结果。

$ cd ch1
$ pytest test_options.py --tb=no -v
这里的--tb=no表示屏蔽报错信息;-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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 3 items                                                          

test_options.py::test_print_being_captured PASSED                    [ 33%]
test_options.py::test_show_captured_when_fail FAILED                 [ 66%]
test_options.py::test_show_locals FAILED                             [100%]

========================= short test summary info ==========================
FAILED test_options.py::test_show_captured_when_fail - assert False
FAILED test_options.py::test_show_locals - assert False
======================= 2 failed, 1 passed in 0.03s ========================
可以看到,第12行显示有2个函数执行失败了,有1个函数执行通过了。

我们现在加上--lf选项,来专门执行那两条失败的用例。

执行命令:

$ cd ch1
$ pytest test_options.py --tb=no -v --lf
这里的--tb=no表示屏蔽报错信息;-v表示显示详细信息,会列出测试函数执行的清单,--lf表示仅执行之前执行结果是失败或错误的测试函数。

输出结果:

=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 3 items / 1 deselected / 2 selected                              
run-last-failure: rerun previous 2 failures

test_options.py::test_show_captured_when_fail FAILED                 [ 50%]
test_options.py::test_show_locals FAILED                             [100%]

========================= short test summary info ==========================
FAILED test_options.py::test_show_captured_when_fail - assert False
FAILED test_options.py::test_show_locals - assert False
===================== 2 failed, 1 deselected in 0.03s ======================
可以看到,现在只是专注于执行之前那两条失败FAILED的测试用例,之前通过PASSED的测试函数就不需要再执行了(所以最后一行显示1 deselected)。

--ff

run all tests, but run the last failures first. 失败优先执行,其余接着执行。

--ff--failed-first选项的简写,会运行全部的测试函数,只是先后顺序有要求:优先执行上次未通过(失败或错误)的测试函数,然后再执行余下的测试。

首先,正常执行一次,观察结果。执行命令:

$ pytest test_options.py --tb=no -v
这里的--tb=no表示屏蔽报错信息;-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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 3 items                                                          

test_options.py::test_print_being_captured PASSED                    [ 33%]
test_options.py::test_show_captured_when_fail FAILED                 [ 66%]
test_options.py::test_show_locals FAILED                             [100%]

========================= short test summary info ==========================
FAILED test_options.py::test_show_captured_when_fail - assert False
FAILED test_options.py::test_show_locals - assert False
======================= 2 failed, 1 passed in 0.03s ========================
可以看到,当前的执行的先后顺序是PASSEDFAILEDFAILED

现在加上--ff选项,优先执行那两条失败的函数,再执行余下的测试函数。

执行命令:

$ pytest test_options.py --tb=no -v --ff
这里的--tb=no表示屏蔽报错信息;-v表示显示详细信息,会列出测试函数执行的清单,--ff表示优先之前执行结果是失败或错误的测试函数,然后执行余下的测试函数。

输出结果:

=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 3 items                                                          
run-last-failure: rerun previous 2 failures first

test_options.py::test_show_captured_when_fail FAILED                 [ 33%]
test_options.py::test_show_locals FAILED                             [ 66%]
test_options.py::test_print_being_captured PASSED                    [100%]

========================= short test summary info ==========================
FAILED test_options.py::test_show_captured_when_fail - assert False
FAILED test_options.py::test_show_locals - assert False
======================= 2 failed, 1 passed in 0.03s ========================
可以看到,加上--ff选项后,执行的先后顺序变成了FAILEDFAILEDPASSED,证明了确实是执行了全部的测试函数, 并且之前失败的测试函数得到了优先执行。

--tb=style

traceback print mode (auto/long/short/line/native/no). 报错信息样式

此选项用来控制测试函数执行失败时报错信息的输出方式。其中tbtraceback单词的缩写。

当一个测试测试执行失败后,pytest 会列出失败结果标志和报错信息,并显示了失败发生的确切位置。尽管报错信息在大多数情况下对调试代码是有帮助的,但是冗长的信息有时看起来有点啰嗦。这时候--tb=style选项就能派上用场了。

该选项所支持的样式有:

样式 含义
--tb=auto 按long样式显示第一次和最后一次跟踪回溯默认样式,其余采用short样式展示。如果有多次失败,这是默认的样式。
--tb=long 展示详尽的报错信息(代码行、断言行、追踪行、定位行)
--tb=short 展示精简的报错信息(定位行、断言行、追踪行)
--tb=line 每个失败函数只展示一行报错信息
--tb=no 屏蔽,不展示任何报错信息
--tb=native 按python语言标准的报错信息格式展示

官方文档:https://docs.pytest.org/en/7.1.x/how-to/output.html

常用的样式有--tb=short--tb=line--tb=no。其中--tb=short相比--tb-long而言,不再展示函数代码行。

ch1目录中的test_two.py文件包含一个典型的失败场景用例,以它为例演示报错信息的各种打印样式。

常规执行命令:

$ pytest test_two.py 
如果不指定样式,默认的样式为--tb=auto

输出结果:

=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                           

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 the full 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 in 0.04s =============================
可以看到,FAILURES报错信息比较详细,在____ test_failing ____函数版块中,分别展示了代码行(第12行)、断言行(第13行)、追踪行(第14~16行)和定位行(第18行)信息。

如果需要精简一些展示信息,可以使用--tb=short选项。

$ pytest test_two.py --tb=short
输出结果:
=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                           

test_two.py F                                                        [100%]

================================= FAILURES =================================
_______________________________ test_failing _______________________________
test_two.py:2: in 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 the full diff
========================= short test summary info ==========================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
============================ 1 failed in 0.04s =============================
可以看到,报错信息的____ test_failing ____函数版块中,分别展示了定位行(第11行)、断言行(第12行)、追踪行(第13~15行)信息。

详在很多情况下,一行报错信息足以看出问题。特别是当有一大堆执行失败的测试结果,--tb=line能大大简化报错信息的展示。

执行命令:

pytest test_two.py --tb=line
输出结果:
=========================== 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/ch1
plugins: allure-pytest-2.9.45, Faker-9.3.1, ordering-0.6
collected 1 item                                                           

test_two.py F                                                        [100%]

================================= FAILURES =================================
/Users/xiaofo/coding/Personal/studypytest/ch1/test_two.py:2: assert (1, 2, 3) == (3, 2, 1)
========================= short test summary info ==========================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
============================ 1 failed in 0.03s =============================
可以看到,报错信息只展示成一行信息了:/Users/xiaofo/coding/Personal/studypytest/ch1/test_two.py:2: assert (1, 2, 3) == (3, 2, 1)。由冒号:号分隔成了3段信息,第一段是测试模块完整名称,第二段是错误所在行号,第三段是断言语句。

--durations=N

show N slowest setup/test durations (N=0 for all). 展示龟速用例

如果测试函数的执行性能是你关注的指标,那么可以使用此选项帮助你筛选出执行效率较慢的那些。它会在测试运行后展示执行耗时最长的 N 个测试函数/前置/后置动作。如果 N=0,则会按照从最慢到最快的顺序展示所有执行结果。

我们当前的测试函数都会很快执行,所以为了演示效果,我们在ch/test_options.py模块中添加几个新的测试用例,并且在它们之中加上休眠时长。

import time

def test_duration_1():
    time.sleep(1)
    assert True


    def test_duration_2():
        time.sleep(2)
        assert True


        def test_duration_3():
            time.sleep(3)
    assert True

执行命令:

$ pytest test_options.py --tb=no --durations=3
备注:--tb=no表示屏蔽繁冗的报错信息;--durations=3表示展示执行速度最慢的3个测试函数。

输出结果:

========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.8.8, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Coding\Gitees\studypytest\ch1
plugins: allure-pytest-2.9.45
collected 6 items                                                                                                                                                       

test_options.py .FF...                                                                                                                                            [100%]

========================================================================= slowest 3 durations ==========================================================================
3.01s call     test_options.py::test_duration_3
2.00s call     test_options.py::test_duration_2
1.01s call     test_options.py::test_duration_1
======================================================================= short test summary info ========================================================================
FAILED test_options.py::test_show_captured_when_fail - assert False
FAILED test_options.py::test_show_locals - assert False
===================================================================== 2 failed, 4 passed in 6.06s ======================================================================
可以看到,slowest 3 durations版块中已经列出了运行最慢(耗时最长)的3个函数。

--fixtures

show available fixtures, sorted by plugin appearance 展示可用的夹具

--setup-show

show setup of fixtures while executing tests. 执行测试时展示夹具配置情况

--cache-show

查看缓存信息

--disable-warnings

关闭警告信息(特别是在使用了尚未注册的标记)


在这一章中,我们讨论了在哪里获得pytest 以及运行它的各种方法。然而,我们没有讨论测试函数的内容。

在下一章中,我们将研究如何编写测试函数,将它们参数化,以便进行数据驱动测试,并将测试分组为类、模块和包。