第 1 章 pytest入门

这是一个测试:

ch1/test_one.py
def test_passing():
    assert (1, 2, 3) == (1, 2, 3)

这看起来很简单。 但仍有很多事情发生。 函数 test_passing() 将被 pytest 发现为测试函数,因为它以 test 开头,并且位于以 test 开头的文件中。 当测试运行时,assert 语句将确定测试是否通过或失败。 assertPython 中内置的关键字,如果断言后的表达式为 false,则具有引发 AssertionError 异常的行为。测试中引发的任何未捕获的异常都会导致测试失败。 尽管任何类型的未捕获异常都可能导致测试失败,但传统上我们坚持使用断言中的 AssertionError 来确定测试的通过/失败。

我们稍后会详细讨论所有这些内容。 首先,我想向您展示在命令行上运行测试的样子。 为了运行这个测试。 我们需要安装 pytest。 所以让我们现在就这样做。

安装 pytest

pytest 的总部是 https://pytest.org 。 这是官方文档。 但它是通过 PyPIPython 包索引)在 https://pypi.org/project/pytest 分发的。

与通过 PyPI 分发的其他 Python 包一样,使用 pippytest 安装到您正在使用的虚拟环境中进行测试:

$ python3 -m venv venv
$ source venv/bin/activate
(venv)$ pip install pytest

在命令提示符之前添加的 (venv) 让您知道您正在使用虚拟环境。 对于本书其余部分的示例,我们始终使用虚拟环境。 然而,为了避免页面上的一点混乱,(venv) 已被删除。 我们也始终使用 python3,但会将其缩短为 python

如果您不熟悉 venvpip,我已经为您介绍了。查看(附录 1,虚拟环境)和(附录 2,pip)。

Windows 怎么办?

venvpip 的示例应该适用于许多 POSIX 系统,例如 Linuxmacos,以及许多版本的 Python,包括 Python 3.7 及更高版本。

请注意,源 venv/bin/activate 行不适用于 Windows。 对于 cmd.exe,请改用 venv\scripts\activate.bat

C:\> python -m venv venv
C:\> venv\Scripts\activate.bat
C:\> pip install pytest

对于 PowerShell,请改用 venv\scripts\Activate.ps1

PS C:\> python -m venv venv
PS C:\> venv\Scripts\Activate.ps1
PS C:\> pip install pytest
virtualenv怎么办

对于某些 Linux 发行版,您将需要使用 virtualenv。 有些人还因为各种原因更喜欢 virtualenv

$ python3 -m pip install virtualenv
$ python3 -m virtualenv venv
$ source venv/bin/activate
(venv)$ pip install pytest

运行 pytest

安装了 pytest 后,我们可以运行 test_passing()。 这是它运行时的样子:

$ cd /path/to/code/ch1
$ pytest test_one.py
==================== test session starts =====================
collected 1 item

test_one.py .                                           [100%]
===================== 1 passed in 0.02s ======================

test_one.py 后面的点表示已运行一项测试并通过。 [100%] 是一个百分比指示器,显示到目前为止测试套件已完成多少。 由于测试环节只有一项测试,因此一项测试等于 100% 的测试。 如果您需要更多信息,可以使用 -v--verbose

$ pytest -v test_one.py
==================== test session starts =====================
collected 1 item

test_one.py::test_passing PASSED                        [100%]
===================== 1 passed in 0.03s ======================

如果您有彩色终端,则 PASSED 且底线显示为绿色。这很好。

下面是一个失败测试:

ch1/test_two.py
def test_failing():
    assert (1, 2, 3) == (3, 2, 1)

pytest 显示测试失败的方式是开发人员喜欢 pytest 的众多原因之一。 让我们看看这个失败:

$ pytest test_two.py

==================== test session starts =====================
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
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 in 0.17s ======================

失败的测试 test_failing 有自己的部分来向我们展示失败的原因。pytest 准确地告诉我们第一个失败是什么:索引 0 不匹配。 如果您有彩色终端,其中大部分都会显示为红色,以使其真正脱颖而出。 这个额外的部分准确显示了测试失败的位置以及一些周围的代码,称为回溯(traceback)。

这已经是很多信息了,但是有一行说 “Use -v to get the full diff”。 让我们这样做:

$ pytest -v test_two.py

==================== test session starts =====================
collected 1 item

test_two.py::test_failing FAILED                        [100%]
========================= FAILURES ===========================
_______________________ test_failing _________________________

    def test_failing():
>       assert (1, 2, 3) == (3, 2, 1)
E       AssertionError: assert (1, 2, 3) == (3, 2, 1)
E
E         At index 0 diff: 1 != 3
E
E         Full diff:
E           (
E         +     1,
E         +     2,...
E
E         ...Full output truncated (4 lines hidden), use '-vv' to show

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

pytest 添加了小插入符 (^) 来向我们展示具体的不同之处。

到目前为止,我们已经使用命令 pytest test_one.pypytest test_two.py 运行 pytest,所以我们知道我们可以给 pytest 一个文件名,它将运行文件中的测试。 让我们再用几种方式来运行它。

要运行 pytest,您可以选择指定文件和目录。 如果您不指定任何文件或目录,pytest 将在当前工作目录和子目录中查找测试。 它查找以 test_ 开头或以 _test 结尾的 .py 文件。 在 ch1 目录中,如果您不使用任何命令运行 pytest,您将运行两个文件的测试:

$ pytest --tb=no

==================== test session starts =====================
collected 2 items

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

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

我还使用 --tb=no 标志来关闭回溯,因为我们现在并不真正需要完整的输出。 我们将在整本书中使用各种标志。

我们还可以通过指定它们或列出目录名称来运行相同的测试集:

$ pytest --tb=no test_one.py test_two.py

==================== test session starts =====================
collected 2 items

test_one.py .                                           [ 50%]
test_two.py F                                           [100%]
================== short test summary info ===================
FAILED test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
================ 1 failed, 1 passed in 0.05s =================

$ cd ..
$ pytest --tb=no ch1
==================== test session starts =====================
collected 2 items

ch1\test_one.py .                                       [ 50%]
ch1\test_two.py F                                       [100%]
================== short test summary info ===================
FAILED ch1/test_two.py::test_failing - assert (1, 2, 3) == (3, 2, 1)
================ 1 failed, 1 passed in 0.04s =================

我们还可以通过在文件名中添加 ::test_name 来指定要运行的测试文件中的测试函数:

$ pytest -v ch1/test_one.py::test_passing
==================== test session starts =====================
collected 1 item

ch1/test_one.py::test_passing PASSED                    [100%]
===================== 1 passed in 0.02s ======================

发现测试

pytest 执行过程中 pytest 启动并查找要运行的测试的部分称为测试发现。pytest 能够找到我们希望它运行的所有测试,因为我们根据 pytest 命名约定命名它们。

如果没有参数,pytest 会查看当前目录和所有子目录中的测试文件,并运行它找到的测试代码。 如果你给 pytest 一个文件名、目录名或这些的列表,它会在那里查找而不是当前目录。 检查命令行上列出的每个目录以及任何子目录是否有测试代码。

以下是命名约定的简要概述,以确保 pytest 可以发现您的测试代码:

  • 测试文件应命名为 test_<something>.py<something>_test.py

  • 测试方法和函数应命名为 test_<something>

  • 测试类应命名为 Test<something>

因为我们的测试文件和函数以 test_ 开头,所以我们很好。 如果您有一堆名称不同的测试,可以通过多种方法来更改这些发现规则。 我将在第 8 章 “配置文件” 中介绍如何执行此操作。

到目前为止,我们已经看到一项通过测试和一项失败测试。但是,通过和失败并不是唯一可能的结果。

测试结果

以下是测试的可能结果:

  • PASSED (.) - 测试运行成功。

  • FAlLED (F) - 测试未成功运行。

  • SKIPPED (s) - 测试被跳过。 您可以使用 @pytest.mark.skip()@pytest.mark.skipif() 装饰器告诉 pytest 跳过测试,这在 【使用 pytest.mark.skip 跳过测试】 中进行了讨论。

  • XFAIL (x) - 测试本不应通过,但运行后失败了。 您可以使用 @pytest.mark.xfail() 装饰器告诉 pytest 测试预计会失败,这在 【Expecting Tests to Fail with pytest.mark.xfail】中进行了讨论。

  • XPASS (X) - 测试被标记为 xfail,但它运行并通过。

  • ERROR (E) - 在执行固定装置或钩子函数期间发生异常,而不是在执行测试函数期间发生异常。 夹具在第 3 章 【pytest 夹具】 中讨论,挂钩函数在第 15 章 【构建插件】 中讨论。

回顾

恭喜!到目前为止,您已经在本章中完成了相当多的工作,并且正在快速掌握 pytest,下面是对本章所涵盖内容的快速回顾:

  • pytest 安装到虚拟环境中,步骤如下:

    • python -m venv venv

    • source venv/bin/active(或 venv\Scripts\activate.bat/venv\Scripts\Activate.ps1)

    • pip install pytest

  • pytest可以以不同的方式运行:

    • pytest:不带任何参数,pytest 会在本地目录和子目录中搜索测试。

    • pytest <filename>:在一个文件中运行测试。

    • pytest <Filename> <Filename>…​:在多个命名文件中运行测试。

    • pytest <dirname>:从特定目录(或多个目录)开始并递归搜索测试。

  • 测试发现是指 pytest 如何找到您的测试代码并取决于命名:

    • 测试文件应命名为 test_<something>.py 或 <something>_test.py。

    • 测试方法和函数应命名为 test_<something> 。

    • 测试类应命名为 Test<something> 。

  • 测试函数的可能结果包括:PASSED(.)、FAILED(F)、SKIPPED(s)、XFAIL(x)、XPASS(X) 和 ERROR(E)。

  • -v 或 --verbose 命令行标志用于显示更详细的输出。

  • --tb=no 命令行标志用于关闭回溯。

练习

本章以及本书的其余部分都是为了让您可以自己跟进而设计的。 最后做练习可以帮助巩固你的学习。 他们应该只需要几分钟。 以下练习旨在

  • 让您熟悉虚拟环境,

  • 确保您可以安装 pytest,并且

  • 让您编写一些测试文件并使用不同类型的断言语句。

pytest 允许您快速编写新的小测试。 亲眼看看自己编写一些测试有多快应该很有趣。 事实上,测试应该很有趣。 现在开始学习如何使用测试代码将帮助您避免对将来编写测试的恐惧。

另外,你真的需要知道你现在安装或运行 pytest 时遇到的任何问题,否则本书的其余部分肯定不会有趣。

  1. 使用 python -m virtualenvpython -m venv 创建新的虚拟环境。 即使你知道你正在从事的项目不需要虚拟环境,请迁就我并充分了解它们,以创建一个用于尝试本书中的内容的虚拟环境。 我很长一段时间都拒绝使用它们,现在我总是使用它们。 如果您遇到任何困难,请阅读 【第 235 页的附录 1“虚拟环境”】。

  2. 练习激活和停用虚拟环境几次。

    • $ source venv/bin/activate

    • $ deactivate

    Windows系统:

    • C:>venv\scripts\activate.bat (or C:>venv\scripts\Activate.ps1 for PowerShell)

    • C:>deactivate

  3. 在新的虚拟环境中安装 pytest。 如果您遇到任何问题,请参阅 【第 237 页的附录 2 pip】。 即使您认为已经安装了 pytest,您也需要将其安装到刚刚创建的虚拟环境中。

  4. 创建一些测试文件。 您可以使用我们在本章中使用的那些,也可以自己制作。 练习对这些文件运行 pytest

  5. 更改断言语句。 不要只使用 assert Something == Something_else; 尝试以下方法:

    • assert 1 in [2,3,4]

    • assert a < b

    • assert 'fizz' not in 'fizzbuzz'

下一步

在本章中,我们了解了从哪里获取 pytest、如何安装它以及运行它的各种方法。 然而,我们没有讨论测试函数的内容。 在下一章中,我们将讨论编写测试函数,并将测试分组到类、模块和目录中。