Skip to content

Latest commit

 

History

History
573 lines (392 loc) · 26.7 KB

File metadata and controls

573 lines (392 loc) · 26.7 KB

四、插件

在上一章中,我们探讨了 pytest 最重要的特性之一:fixture。我们学习了如何使用 fixture 来管理资源,并在编写测试时使我们的生活更轻松。

pytest 的构建考虑了定制和灵活性,允许开发人员编写名为插件的强大扩展。pytest 中的插件可以做各种事情,从简单地提供一个新的 fixture,一直到添加命令行选项,更改测试的执行方式,甚至运行用其他语言编写的测试。

在本章中,我们将执行以下操作:

  • 了解如何查找和安装插件
  • 体验一下生态系统提供的插件

查找和安装插件

正如本章开头提到的,pytest 是从头开始编写的,考虑到定制和灵活性。插件机制是 pytest 体系结构的核心,以至于 pytest 的许多内置功能都是通过内部插件实现的,比如标记、参数化、夹具几乎所有东西,甚至命令行选项。

这种灵活性导致了一个巨大而丰富的插件生态系统。在撰写本文时,可用插件的数量超过了 500 个,而且这个数字还在以惊人的速度增长

查找插件

考虑到大量插件,如果有一个站点显示所有 pytest 插件及其描述,那就太好了。如果这个地方也展示了关于与不同 Python 和 pytest 版本兼容的信息,那就太好了。

好消息是这样一个网站已经存在,并且由核心开发团队维护:pytest 插件兼容性(http://plugincompat.herokuapp.com/ )。在上面,您将找到 PyPI 中可用的所有 pytest 插件的列表,以及 Python 和 pytest 版本兼容性信息。该网站每天都会直接从 PyPI 获得新插件和更新,这使它成为浏览新插件的好地方。

安装插件

插件通常与pip一起安装:

λ pip install <PLUGIN_NAME>

例如,要安装pytest-mock,我们执行以下操作:

λ pip install pytest-mock

无需任何形式的注册;pytest 会自动检测虚拟环境或 Python 安装中已安装的插件

这种简单性使得尝试新插件非常容易

各种插件概述

现在,我们来看看一些有用和/或有趣的插件。当然,这里不可能涵盖所有的插件,因此我们将尝试涵盖那些涵盖流行框架和一般功能的插件,并加入一些不知名的插件。当然,这只是表面现象,但让我们开始吧。

pytest xdist

这是一个非常流行的插件,由核心开发人员维护;它允许您在多个 CPU 下运行测试,以加快测试运行。

安装后,只需使用-n命令行标志使用给定数量的 CPU 来运行测试:

λ pytest -n 4

就这样!现在,您的测试将运行在四个内核上,如果测试套件是 CPU 密集型的,那么希望能够大大加快测试套件的速度,尽管 I/O 绑定测试不会有太大的改进。您也可以使用-n auto让pytest-xdist自动计算出您可用的 CPU 数量。

Keep in mind that when your tests are running concurrently, and in random order, they must be careful to avoid stepping on each other's toes, for example, reading/writing to the same directory. While they should be idempotent anyway, running the tests in a random order often brings attention to problems that were lying dormant until then. 

pytest 冠状病毒

pytest-cov插件提供了与流行的覆盖率模块的集成,该模块在运行测试时为您的代码提供详细的覆盖率报告。这使您能够检测任何测试代码都没有覆盖的代码部分,这是编写更多测试以覆盖这些情况的机会。

安装后,您可以使用--cov选项在测试运行结束时提供覆盖率报告:

λ pytest --cov=src
...
----------- coverage: platform win32, python 3.6.3-final-0 -----------
Name                  Stmts   Miss  Cover
----------------------------------------
src/series.py           108      5   96%
src/tests/test_series    22      0  100%
----------------------------------------
TOTAL                   130      5   97%

--cov选项接受应该生成报告的源文件路径,因此您应该根据项目的布局传递src或包目录

您还可以使用--cov-report选项生成各种格式的报告:XML、注释和 HTML。后者在本地使用尤其有用,因为它生成显示代码的 HTML 文件,并以红色突出显示缺失的行,从而很容易找到那些未覆盖的点。

这个插件也可以与pytest-xdist一起使用。

最后,该插件生成的.coverage文件与许多提供覆盖率跟踪和报告的在线服务兼容,如coveralls.io(https://coveralls.io/ 和codecov.iohttps://codecov.io/ )。

pytest faulthandler

此插件自动启用内置的faulthandler(https://docs.python.org/3/library/faulthandler.html )模块运行测试时,该模块在灾难性情况下(如分段错误)输出 Python 回溯。安装后,不需要其他设置或标志;faulthandler模块将自动启用。

如果您经常使用用 C/C++编写的扩展模块,强烈建议使用此插件,因为这些模块更容易崩溃。

pytest 模拟

pytest-mock插件提供了一个夹具,允许 pytest 和unittest.mock(之间进行更平滑的集成 https://docs.python.org/3/library/unittest.mock.html 标准库的模块。它提供了类似于内置monkeypatch装置的功能,但unittest.mock产生的模拟对象也记录了如何访问它们的信息。这使得许多常见的测试任务变得更容易,例如验证是否调用了模拟函数,以及使用了哪些参数。

该插件提供了一个mocker夹具,可用于修补类和方法。使用上一章中的getpass示例,下面是如何使用此插件编写它:

import getpass

def test_login_success(mocker):
    mocked = mocker.patch.object(getpass, "getpass", 
                                 return_value="valid-pass")
    assert user_login("test-user")
    mocked.assert_called_with("enter password: ")

请注意,除了替换getpass.getpass()并始终返回相同的值外,我们还可以确保使用正确的参数调用了getpass函数。

使用此插件时,上一章中关于如何以及在何处修补monkeypatch夹具的建议也适用。

pytest django

顾名思义,这个插件允许你测试你的Django(https://www.djangoproject.com/ 使用 pytest 的应用程序。Django是当今使用的最著名的 web 框架之一。

该插件提供了大量功能:

  • 一个非常好的快速入门教程
  • 用于配置 Django 的命令行和pytest.ini选项
  • 与pytest-xdist的兼容性
  • 使用django_db标记进行数据库访问,测试之间自动事务回滚,以及一系列装置,让您可以控制如何管理数据库
  • 向您的应用程序发出请求的装置:client、admin_client和admin_user
  • 在后台线程中运行Django服务器的live_server设备

总而言之,这是生态系统中最完整的插件之一,这里有太多的功能。它是Django应用程序的必备工具,因此请务必查看其丰富的文档。

最细小的薄片

此插件允许您使用pyflakes(检查您的代码 https://pypi.org/project/pyflakes/ ),它是源文件的静态检查器,用于检查常见错误,例如缺少导入和未知变量。

安装后,使用--flakes选项将其激活:

λ pytest pytest-flakes.py --flake
...
============================= FAILURES ==============================
__________________________ pyflakes-check ___________________________
CH5\pytest-flakes.py:1: UnusedImport
'os' imported but unused
CH5\pytest-flakes.py:6: UndefinedName
undefined name 'unknown'

这将在正常测试的同时运行鳞片检查,使其成为保持代码整洁和防止某些错误的简单而廉价的方法。该插件还保留了自上次检查以来未更改的文件的本地缓存,因此在本地使用它既快捷又方便。

pytest 异步

asyncio(https://docs.python.org/3/library/asyncio.html )模块是 Python3 的热门新增功能之一,为异步应用程序提供了一个新的框架。pytest-asyncio插件可以让你编写异步测试函数,让你快速测试异步代码。

您只需制作测试函数async def并用asyncio标记:

@pytest.mark.asyncio
async def test_fetch_requests():
    requests = await fetch_requests("example.com/api")
    assert len(requests) == 2

该插件还可以在后台管理事件循环,如果需要使用自定义事件循环,它提供了一些关于如何更改事件循环的选项。

You are, of course, free to have normal synchronous test functions along with the asynchronous ones.

皮特斯特三重奏

Trio 的座右铭是针对人类的 Python 异步 I/O(https://trio.readthedocs.io/en/latest/ )。它使用了与asyncio标准模块相同的async def/await关键字,但它被认为更简单、更友好,包含了一些关于如何处理超时和并行任务组的新颖想法,以避免并行编程中的常见错误。如果您对异步开发感兴趣,那么这绝对值得一看。

pytest-trio的工作原理与pytest-asyncio类似:您编写异步测试函数,并使用trio标记对其进行标记。它还提供了使测试更容易和更可靠的其他功能,例如用于测试超时的可控时钟、处理任务的特殊功能、模拟网络套接字和流等。

最猛烈的龙卷风

龙卷风(http://www.tornadoweb.org/en/stable/ 是一个 web 框架和异步网络库。它非常成熟,在 Python2 和 Python3 中工作,标准的asyncio模块从中借鉴了许多思想和概念。

pytest-asyncio深受pytest-tornado的启发,因此其工作原理与使用gen_test将您的测试标记为协同程序的想法相同。它使用yield关键字而不是await,因为它支持 Python 2,但在其他方面看起来非常相似:

@pytest.mark.gen_test
def test_tornado(http_client):
    url = "https://docs.pytest.org/en/latest"
    response = yield http_client.fetch(url)
    assert response.code == 200

pytest postgresql

此插件允许您测试需要运行 PostgreSQL 数据库的代码。

下面是一个快速的例子:

def test_fetch_series(postgresql):
    cur = postgresql.cursor()
    cur.execute('SELECT * FROM comedy_series;')
    assert len(cur.fetchall()) == 5
    cur.close()

它提供了两个装置:

  • postgresql:启动和关闭与运行测试数据库连接的客户端设备。在测试结束时,它会删除测试数据库,以确保测试不会相互干扰。
  • postgresql_proc:会话范围的固定装置,每个会话启动一次 PostgreSQL 进程,并确保它在结束时停止。

它还提供了几个关于如何连接和配置测试数据库的配置选项。

码头服务

该插件启动并管理您测试代码所需的 Docker 服务。这使得运行测试变得简单,因为您不需要自己手动启动服务;插件将根据需要在测试会话期间启动和停止它们。

您使用.services.yaml文件配置服务;下面是一个简单的例子:

database:
    image: postgres
    environment:
        POSTGRES_USERNAME: pytest-user
        POSTGRES_PASSWORD: pytest-pass
        POSTGRES_DB: test
    image: regis:10

这将启动两个服务:postgres和redis。

有了这些,剩下要做的就是使用以下工具运行您的套件:

pytest --docker-services

插件会处理其余部分。

微量硒

Selenium 是一个旨在自动化浏览器、测试 web 应用程序的框架(https://www.seleniumhq.org/ )。它允许您以编程方式打开网页、单击按钮,然后确保加载特定页面。它支持所有主流浏览器,拥有一个蓬勃发展的社区。

pytest-selenium为您提供了一个装置,允许您编写完成所有这些工作的测试,并负责为您设置Selenium。

以下是如何访问页面、单击链接并检查加载页面标题的基本示例:

def test_visit_pytest(selenium):
    selenium.get("https://docs.pytest.org/en/latest/")
    assert "helps you write better programs" in selenium.title
    elem = selenium.find_element_by_link_text("Contents")
    elem.click()
    assert "Full pytest documentation" in selenium.title

Selenium和pytest-selenium非常复杂,足以测试各种应用程序,从静态页面到完整的单页前端应用程序。

pytest html

pytest-html生成漂亮的测试结果 HTML 报告。安装插件后,只需运行以下命令:

λ pytest --html=report.html

这将在测试会话结束时生成一个report.html文件。

因为图片胜于文字,这里有一个例子:

这些报告可以在 web 服务器中提供,以便于查看,此外,它们还包含一些很好的功能,如用于显示/隐藏不同类型测试结果的复选框,以及其他插件,如pytest-selenium甚至可以将屏幕截图附加到失败的测试上,如上图所示。

这绝对值得一看。

pytest cpp

为了证明 pytest 的框架非常灵活,pytest-cpp插件允许您运行用 Google Test(编写的测试 https://github.com/google/googletest 或升压试验(https://www.boost.org Apple T4)Ty6 T6,这是 C++语言编写和运行测试的框架。

安装后,您只需正常运行 pytest 即可:

λ pytest bin/tests

Pytest 将查找包含测试用例的可执行文件,自动检测它们是在Google Test还是Boost.Python中编写的。它将正常运行测试并报告结果,具有 pytest 用户熟悉的整洁格式。

使用 pytest 运行这些测试意味着它们现在可以使用几个功能,例如使用pytest-xdist并行运行、使用-k选择测试、JUnitXML 报告等等。这个插件对于使用 Python 和 C++的代码库特别有用,因为它允许您用单个命令运行所有测试,并且可以获得唯一的报告。

pytest 超时

pytest-timeout插件在达到某个超时后自动终止测试

您可以通过在命令行中设置全局超时来使用它:

λ pytest --timeout=60

或者您可以使用@pytest.mark.timeout标记标记单个测试:

@pytest.mark.timeout(600)
def test_long_simulation():
   ...

它使用以下两种方法之一实现超时机制:

  • thread:在测试设置期间,插件启动一个线程,该线程在所需的超时时间内休眠。如果线程被唤醒,它会将所有线程的回溯转储到stderr并终止当前进程。如果测试在线程唤醒之前完成,那么线程将被取消,测试运行将继续。这是一种适用于所有平台的方法。
  • signal:在测试设置过程中安排SIGALRM,测试结束后取消SIGALRM。如果报警被触发,它将把所有线程的回溯转储到stderr并使测试失败,但它将允许测试运行继续。与 thread 方法相比,它的优点是在超时发生时不会取消整个运行,但并非所有平台都支持它。

该方法是基于平台自动选择的,但可以在命令行中更改,也可以通过将method=参数传递到@pytest.mark.timeout来更改每次测试。

这个插件在大型测试套件中是必不可少的,以避免测试挂起 CI。

pytest 注释

Pyannotate(https://github.com/dropbox/pyannotate 是一个观察运行时类型信息的项目,可以使用该信息在源代码中插入类型注释,pytest-annotate使 pytest 易于使用。

让我们回到这个简单的测试用例:

def highest_rated(series):
    return sorted(series, key=itemgetter(2))[-1][0]

def test_highest_rated():
    series = [
        ("The Office", 2005, 8.8),
        ("Scrubs", 2001, 8.4),
        ("IT Crowd", 2006, 8.5),
        ("Parks and Recreation", 2009, 8.6),
        ("Seinfeld", 1989, 8.9),
    ]
    assert highest_rated(series) == "Seinfeld"

安装pytest-annotate后,我们可以生成一个通过--annotations-output标志的注释文件:

λ pytest --annotate-output=annotations.json

这将像往常一样运行测试套件,但它将收集类型信息供以后使用。

之后,您可以调用PyAnnotate将类型信息直接应用于源代码:

λ pyannotate --type-info annotations.json -w
Refactored test_series.py
--- test_series.py (original)
+++ test_series.py (refactored)
@@ -1,11 +1,15 @@
 from operator import itemgetter
+from typing import List
+from typing import Tuple

 def highest_rated(series):
+    # type: (List[Tuple[str, int, float]]) -> str
 return sorted(series, key=itemgetter(2))[-1][0]

 def test_highest_rated():
+    # type: () -> None
 series = [
 ("The Office", 2005, 8.8),
 ("Scrubs", 2001, 8.4),
Files that were modified:
pytest-annotate.py

快速有效地注释一个大型代码库是非常好的,特别是如果测试很好地覆盖了这个代码库。

pytest qt

pytest-qt插件允许您为在Qt框架(中编写的 GUI 应用程序编写测试 https://www.qt.io/ ),支持Qt:PyQt4/PyQt5和PySide/PySide2更流行的 Python 绑定集

它提供了一个qtbot装置,该装置具有与 GUI 应用程序交互的方法,例如单击按钮、在字段中输入文本、等待窗口弹出等。下面是一个快速示例,展示了它的实际应用:

def test_main_window(qtbot):
    widget = MainWindow()
    qtbot.addWidget(widget)

    qtbot.mouseClick(widget.about_button, QtCore.Qt.LeftButton)
    qtbot.waitUntil(widget.about_box.isVisible)
    assert widget.about_box.text() == 'This is a GUI App'

在这里,我们创建一个窗口,单击 about 按钮,等待 about 框出现,然后确保它显示我们期望的文本

它还包含其他好吃的东西:

  • 公用设施等待特定的Qt信号
  • 虚拟方法中错误的自动捕获
  • 自动捕获Qt日志消息

随机测试

理想情况下,测试应该彼此独立,确保自己完成后进行清理,以便可以以任何顺序运行,并且不会以任何方式影响彼此

pytest-randomly通过随机排序测试,在每次运行测试套件时更改测试顺序,帮助您保持测试套件的真实性。这有助于检测测试是否隐藏了其他情况下找不到的相互依赖关系。

它在模块级、类级和函数级上对测试项的顺序进行洗牌。它还将每次测试前的random.seed()重置为一个固定的数字,如测试部分开头所示。稍后可以使用随机种子来复制与--randomly-seed命令行相同的顺序,以复制故障。

作为额外奖励,它还对factory boy(提供特殊支持 https://factoryboy.readthedocs.io/en/latest/reference.html 、faker(https://pypi.python.org/pypi/faker 、及numpy(http://www.numpy.org/ 库,在每次测试前重置其随机状态。

pytest 数据目录

通常,测试需要一个支持文件,例如一个包含喜剧系列数据的 CSV 文件,正如我们在上一章中看到的。pytest-datadir允许您将文件与测试一起保存,并以安全的方式从测试中轻松访问它们。

假设您的文件结构如下:

tests/
    test_series.py

除此之外,您还需要从test_series.py中定义的测试中访问一个series.csv文件。

安装pytest-datadir后,只需在同一目录下创建一个测试文件名为的目录,并将该文件放在该目录下:

tests/
 test_series/
 series.csv
    test_series.py

test_series目录和series.csv应保存到您的版本控制系统中。

现在,test_series.py中的测试可以使用datadir夹具访问文件:

def test_ratings(datadir):
    with open(datadir / "series.csv", "r", newline="") as f:
        data = list(csv.reader(f))
    ...

datadir是指向数据目录(的路径实例 https://docs.python.org/3/library/pathlib.html 。

需要注意的一点是,当我们在测试中使用datadirfixture 时,我们访问的不是原始文件的路径,而是一个临时副本。这确保测试可以修改数据目录中的文件,而不会影响其他测试,因为每个测试都有自己的副本。

pytest 回归

通常情况下,应用程序或库包含生成数据集作为结果的功能。

测试这些结果通常是乏味且容易出错的,产生如下测试:

def test_obtain_series_asserts():
    data = obtain_series()
    assert data[0]["name"] == "The Office"
    assert data[0]["year"] == 2005
    assert data[0]["rating"] == 8.8
    assert data[1]["name"] == "Scrubs"
    assert data[1]["year"] == 2001
    ...

这个很快就变老了。此外,如果任何断言失败,那么测试将在该点停止,并且您将不知道在该点之后的任何其他断言是否也会失败。换言之,您无法清楚地了解总体失败情况。最重要的是,这也是很难维护的,因为如果obtain_series()返回的数据发生变化,您将面临更新所有代码的繁琐且容易出错的任务。

pytest-regressions提供了解决此类问题的夹具。一般数据(如前一示例)是data_regression夹具的作业:

def test_obtain_series(data_regression):
    data = obtain_series()
    data_regression.check(data)

第一次执行此测试时,它将失败,并显示如下消息:

...
E Failed: File not found in data directory, created:
E - CH5\test_series\test_obtain_series.yml

它将以格式良好的 YAML 文件形式将传递给data_regression.check()的数据转储到test_series.py文件的数据目录中(由我们前面看到的pytest-datadir夹具提供):

- name: The Office
  rating: 8.8
  year: 2005
- name: Scrubs
  rating: 8.4
  year: 2001
- name: IT Crowd
  rating: 8.5
  year: 2006
- name: Parks and Recreation
  rating: 8.6
  year: 2009
- name: Seinfeld
  rating: 8.9
  year: 1989

下次运行此测试时,data_regression现在将传递给data_regressions.check()的数据与数据目录中test_obtain_series.yml中的数据进行比较。如果它们匹配,则测试通过。

但是,如果数据发生更改,测试将失败,新数据和记录的数据之间存在格式良好的文本差异:

E AssertionError: FILES DIFFER:
E ---
E
E +++
E
E @@ -13,3 +13,6 @@
E
E  - name: Seinfeld
E    rating: 8.9
E    year: 1989
E +- name: Rock and Morty
E +  rating: 9.3
E +  year: 2013

在某些情况下,这可能是一种回归,在这种情况下,您可以查找代码中的错误。

但在这种情况下,新数据是*正确的;*您只需使用--force-regen标志运行 pytest,pytest-regressions将为您更新数据文件的新内容:

E Failed: Files differ and --force-regen set, regenerating file at:
E - CH5\test_series\test_obtain_series.yml

现在,如果我们再次运行它,测试就会通过,因为该文件包含新数据。

当您有几十个测试突然产生不同但正确的结果时,这将大大节省时间。只需执行一次 pytest,就可以使它们全部更新。

我自己使用这个插件,我无法计算它为我节省了多少时间。

荣誉奖

好的插件太多了,无法放入本章。前面的示例只是一个小尝试,我试图在有用、有趣和显示插件架构的灵活性之间取得平衡。

下面是一些值得一提的其他插件:

  • pytest-bdd:pytest 的行为驱动开发

  • pytest-benchmark:基准代码的固定装置。它输出带有颜色输出的基准测试结果

  • pytest-csv:将测试状态输出为 CSV 文件

  • pytest-docker-compose:在测试运行期间使用 Docker compose 管理 Docker 容器

  • pytest-excel:在 Excel 中输出测试状态报告

  • pytest-git:为需要处理 git 存储库的测试提供 git 夹具

  • pytest-json:将测试状态输出为 json 文件

  • pytest-leaks:通过反复运行测试并比较引用计数来检测内存泄漏

  • pytest-menu:允许用户从控制台的菜单中选择要运行的测试

  • pytest-mongo:MongoDB 的工艺和客户夹具

  • pytest-mpl:测试 Matplotlib 输出的图形的插件

  • pytest-mysql:MySQL 的流程和客户端设备

  • pytest-poo:将测试失败的F字符替换为“poo 堆”表情符号

  • pytest-rabbitmq:RabbitMQ 的工艺和客户夹具

  • pytest-redis:Redis 的流程和客户夹具

  • pytest-repeat:重复所有测试或特定测试多次,以发现间歇性故障

  • pytest-replay:保存测试运行并允许用户稍后执行,以便重现崩溃和不稳定的测试

  • pytest-rerunfailures:标记可以多次运行的测试,以消除片状测试

  • pytest-sugar:通过添加进度条、表情符号、即时失败等,改变 pytest 控制台的外观

  • pytest-tap:TAP 格式的 toutputs 测试报告

  • pytest-travis-fold:折叠 Travis CI 构建日志中捕获的输出和覆盖率报告

  • pytest-vagrant:适用于流浪箱的 pytest 夹具

  • pytest-vcr:自动管理VCR.py卡带(https://vcrpy.readthedocs.io/en/latest/ ),使用简单标记

  • pytest-virtualenv:这提供了一个 VirtualNV 装置来管理测试中的虚拟环境

  • pytest-watch:持续监视源代码中的更改并重新运行 pytest

  • pytest-xvfb:为您的 UI 测试运行Xvfb(虚拟帧缓冲区)

  • tavern:tan 是否使用基于 YAML 的语法对 API 进行自动测试

  • xdoctest:重写内置 doctests 模块,使 doctests 更容易编写,配置更简单

请记住,在撰写本文时,可用的 pytest 插件数量超过 500 个,因此请务必浏览插件列表,以便找到您喜欢的插件。

总结

在本章中,我们了解了查找和安装插件是多么容易。我们还展示了一些我每天都在使用的插件,并发现它们很有趣。我希望这能让您体验一下 pytest 中的可能性,但是请浏览大量插件,看看是否能找到有用的插件。

创建您自己的插件并不是本书讨论的主题,但如果您感兴趣,以下是一些资源,可以帮助您开始:

在下一章中,我们将学习如何将 pytest 与现有的基于unittest的测试套件一起使用,包括关于如何迁移它们和增量使用 pytest 更多功能的提示和建议。