自动化测试框架pytest教程3-pytest Fixture(夹具)

pytest夹具

现在你已经用pytest编写和运行了测试函数,让我们把注意力转移到称为fixtures的测试辅助函数上,它对几乎所有非微不足道的软件系统的测试代码结构都是至关重要的。fixtures是在实际测试函数之前(有时是之后)由pytest运行的函数。固定程序中的代码可以做任何你想做的事情。你可以使用fixtures来获取测试所需的数据集。你可以使用fixtures在运行测试之前让系统进入已知的状态。fixtures也被用来为多个测试准备数据。

在本章中,你将学习如何创建fixtures,并学习如何使用它们。

Fixture入门

ch3/test_fixtures.py

import pytest


@pytest.fixture()
def some_data():
    """Return answer to ultimate question."""
    return 42

def test_some_data(some_data):
    """Use fixture return value in a test."""
    assert some_data == 42

@pytest.fixture()装饰器用于告诉pytest此函数是Fixture。pytest在运行测试之前要运行它。Fixture可以做工作,也可以向测试函数返回数据。

pytest使用装饰器来为其他函数添加功能和特性。在本例中pytest.fixture() 装饰 some_data() 函数。test_some_data()将some_data作为参数。

在编程和测试社区,甚至在 Python 社区,术语Fixture 有很多含义。我交替使用 "fixture"、"fixture函数 "和 "fixture方法 "来指称本章中讨论的@pytest.fixture()装饰的函数。Fixture也可以用来指代被fixture函数设置的资源。fixture函数经常设置或检索一些测试可以使用的数据。有时这些数据被认为是一个夹具。例如,Django社区经常使用fixture来指一些在应用程序开始时被加载到数据库的初始数据。

不管其他含义如何,在pytest和本书中,测试夹具指的是pytest提供的机制,允许从你的测试函数中分离出 "准备 "和 "清理 "代码。

与测试函数相比,pytest在Fixture中对异常的处理方式不同。在测试代码中发生的异常(或断言失败或对pytest.fail()的调用)会导致 "失败 "的结果。然而,在Fixture中,测试函数被报告为 "错误"。这个区别在调试为什么测试没有通过时是有帮助的。如果测试的结果是 "失败",那么失败是在测试函数的某个地方(或函数调用的东西)。如果测试的结果是 "错误",那么故障是在Fixture的某个地方。

pytest Fixture是使pytest在其他测试框架中脱颖而出的独特的核心功能之一,也是许多人转向并留在pytest的原因。关于Fixture有很多特点和细微差别。一旦你对它们的工作方式有了良好的心理模型,它们对你来说就会显得很容易。然而,你必须玩一段时间才能达到这个目的,所以接下来让我们来做这个。

使用Fixture进行Setup和Teardown

Fixture将对我们测试Cards应用程序有很大的帮助。

ch3/test_count_initial.py

from pathlib import Path
from tempfile import TemporaryDirectory
import cards

import pytest


@pytest.fixture()
def cards_db():
    with TemporaryDirectory() as db_dir:
        db_path = Path(db_dir)
        db = cards.CardsDB(db_path)
        yield db
        db.close()


def test_empty(cards_db):
    assert cards_db.count() == 0



def test_two(cards_db):
    cards_db.add_card(cards.Card("first"))
    cards_db.add_card(cards.Card("second"))
    assert cards_db.count() == 2

为了调用 count() ,我们需要一个数据库对象,我们通过调用 cards.CardsDB(db_path) 得到它。cards.CardsDB() 函数是构造函数;它返回CardsDB 对象。db_path 参数需要是一个 pathlib.Path 对象,指向数据库目录。pathlib 模块在 Python 3.4 中引入,pathlib.Path对象是表示文件系统路径的标准方式。对于测试来说,临时目录是可行的,我们从tempfile.TemporaryDirectory()中得到。

这个测试函数真的不是很痛苦。它只有几行代码。让我们来看看这些问题。在我们调用count()之前,有一些代码是用来设置数据库的,这并不是我们真正想要测试的东西。在断言语句前有对db.close()的调用。把它放在函数的最后似乎更好,但我们必须在assert之前调用它,因为如果assert语句失败,它就不会被调用。

Fixture在使用它们的测试之前运行。如果在函数中出现了yield,它就在那里停止,将控制权传给测试,并在测试完成后继续运行。

在我们的例子中,产生发生在一个带有临时目录块的上下文管理器中。该目录在夹具使用和测试运行时保持不变。测试结束后,控制权回到夹具上,db.close()可以运行,然后with块可以完成并清理目录。

用-setup-show追踪Fixture的执行情况

pytest提供了命令行标志--setup-show,它可以显示测试和Fixture的操作顺序。


$ pytest --setup-show test_count.py
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0
collected 2 items

test_count.py
SETUP    S _session_faker
        SETUP    F cards_db
        ch3/test_count.py::test_empty (fixtures used: _session_faker, cards_db, request).
        TEARDOWN F cards_db
        SETUP    F cards_db
        ch3/test_count.py::test_two (fixtures used: _session_faker, cards_db, request).
        TEARDOWN F cards_db
TEARDOWN S _session_faker

============================== 2 passed in 0.17s ==============================

指定Fixture范围

每个Fixture都有特定的范围,它定义了相对于使用该Fixture的所有测试功能的运行,设置和拆除的顺序。当它被多个测试功能使用时,范围决定了设置和拆除的运行频率。

Fixture的默认范围是函数范围。这意味着夹具的设置部分将在每个需要它的测试运行之前运行。同样地,在测试完成后,每个测试的拆解部分也会运行。

然而,有时你可能不希望发生这种情况。也许设置和连接数据库很耗时,或者你正在生成大量的数据集,或者你正在从服务器或慢速设备中检索数据。真的,你可以在一个Fixture内做任何你想做的事情,而其中一些可能很慢。

让我们改变我们的Fixture的范围,使数据库只被打开一次,在fixture装饰器中添加scope="module"。

test_mod_scope.py

from pathlib import Path
from tempfile import TemporaryDirectory
import cards
import pytest


@pytest.fixture(scope="module")
def cards_db():
    with TemporaryDirectory() as db_dir:
        db_path = Path(db_dir)
        db = cards.CardsDB(db_path)
        yield db
        db.close()


def test_empty(cards_db):
    assert cards_db.count() == 0


def test_non_empty(cards_db):
    cards_db.add_card(cards.Card("first"))
    cards_db.add_card(cards.Card("second"))
    assert cards_db.count() == 2

fixture装饰器的范围参数不只function和module。还有class,package和session。默认的范围是function。

对于定义在测试模块中的fixture,会话和包的作用域与模块作用域一样。为了使用这些其他作用域,我们需要把它们放在conftest.py文件中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容