[跟着官方文档学pytest][五][学习笔记]

举报
John2021 发表于 2022/05/05 22:55:14 2022/05/05
【摘要】 1.请求fixtures在基本级别,测试函数通过将它们声明为参数来请求所需的fixtures。当pytest运行测试时,会查看该测试函数签名中的参数,然后搜索与这些参数同名的fixtures。pytest找到后会运行这些fixtures,捕获它们返回的内容(如果有的话),并将这些对象作为参数传递给测试函数中。 1.1.例子import pytestclass Fruit: def _...

1.请求fixtures

在基本级别,测试函数通过将它们声明为参数来请求所需的fixtures。
当pytest运行测试时,会查看该测试函数签名中的参数,然后搜索与这些参数同名的fixtures。pytest找到后会运行这些fixtures,捕获它们返回的内容(如果有的话),并将这些对象作为参数传递给测试函数中。

1.1.例子

import pytest
class Fruit:
    def __init__(self,name):
        self.name=name
        self.cubed=False
    def cube(self):
        self.cubed=True
class FruitSalad:
    def __init__(self,*fruit_bowl):
        self.fruit=fruit_bowl
        self._cube_fruit()
    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()
# Arrange
@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"),Fruit("banana")]
def test_fruit_salad(fruit_bowl):
    #Act
    fruit_salad=FruitSalad(*fruit_bowl)
    #Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

在这个例子中,test_fruit_salad请求fruit_bowl(即def test_fruit_salad(fruit_bowl):),当pytest看到时,会执行fruit_bowl的fixture函数,并将它返回的对象作为fruit_bowl参数传递给test_fruit_salad。
以下代码演示了如果我们手动完成操作时所发生的情况:

def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]
def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)
    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)
# Arrange
bowl = fruit_bowl()
test_fruit_salad(fruit_bowl=bowl)

1.2.fixtures可以请求其他fixtures

pytest的最大优势之一就是极其灵活的fixture系统。它允许我们将复杂的测试需求归纳为更简单和有组织的功能,我们只需要让每个需求描述它们所依赖的东西。以下例子演示了fixtures如何调用其他fixtures。

# content of test_append.py
import pytest
# Arrange
@pytest.fixture
def first_entry():
    return "a"
# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry] # 相当于return ["a"]
def test_string(order):
    # Act
    order.append("b")
    # Assert
    assert order==["a","b"]

请注意,这是和上面相似的示例。pytest中的fixtures请求fixtures就像测试一样。所有相同的请求规则都适用于用于测试的fixtures。下面是如果我们手动执行此示例的工作原理:

def first_entry():
    return "a"


def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


entry = first_entry()
the_list = order(first_entry=entry)
test_string(order=the_list)

1.3.fixtures可以复用

使pytest的fixture系统强大的原因之一是,它使我们能够定义一个可以一遍又一遍重复使用的通用设置步骤,就像使用普通功能一样。两个不同的测试可以请求相同的fixture,并让pytest从fixture中为每个测试提供自己的结果。
这对于确保测试不受彼此影响非常有用。我们可以使用此系统来确保每个测试都获得自己的数据,并从干净的状态开始,以便它能够提供一致,可重复的结果。
以下是一个如何派上用场的示例:

# contents of test_append1.py
import pytest
# Arrange
@pytest.fixture
def first_entry():
    return "a"
# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry]
def test_string(order):
    # Act
    order.append("b")
    # Assert
    assert order==["a","b"]
def test_int(order):
    # Act
    order.append(2)
    # Assert
    assert order==["a",2]

每个测试都为其提供了该列表对象的自己的副本,意味着order fixture将执行两次(对于first_entry fixture也一样)。如果我们手动执行此操作,如下所示:

def first_entry():
    return "a"


def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


def test_int(order):
    # Act
    order.append(2)

    # Assert
    assert order == ["a", 2]


entry = first_entry()
the_list = order(first_entry=entry)
test_string(order=the_list)

entry = first_entry()
the_list = order(first_entry=entry)
test_int(order=the_list)

1.4.一个测试/fixture可以在一个时刻请求多个fixture

测试和fixtures不仅限于一次请求一个fixture。可以随意请求。如下示例所示:

# contents of test_append2.py
import pytest
# Arrange
@pytest.fixture
def first_entry():
    return "a"
# Arrange
@pytest.fixture
def second_entry():
    return 2
# Arrange
@pytest.fixture
def order(first_entry,second_entry):
    return [first_entry,second_entry]
# Arrange
@pytest.fixture
def expected_list():
    return ["a",2,3.0]
def test_string(order,expected_list):
    # Act
    order.append(3.0)
    # Assert
    assert order==expected_list

1.5.每次测试可以多次请求fixture(缓存返回值)

在同一测试期间,也可以多次请求fixtures,pytest不会为该测试再次执行它们。意味着我们可以在依赖于它们的多个fixtures中请求fixtures(甚至在测试本身中再次请求fixtures),而无需多次执行执行该fixtures。

# contents of test_append3.py
import pytest
# Arrange
@pytest.fixture
def first_entry():
    return "a"
# Arrange
@pytest.fixture
def order():
    return []
# Act
@pytest.fixture
def append_first(order,first_entry):
    return order.append(first_entry)
def test_string_only(append_first,order,first_entry):
    # Assert
    assert order==[first_entry]

如果请求的fixture在测试期间每次请求都执行一次,则此测试将失败,以为append_first和test_string_only都将order视为空列表(即[]),但由于order的返回值在第一次调用后被缓存(以及执行它的任何副作用),测试和append_first都引用了同一个对象,测试看到了append_first对该对象的影响。

2.自动使用fixtures(你不必请求fixtures)

有时,你希望有一个(甚至几个)fixture,知道所有测试都将依赖于该fixture。"Autouse"fixtures是使所有测试自动请求它们的快捷方式。可以减少大量冗余请求。
可以通过将autouse=True传递给fixtures的装饰器。以下是一个简单的示例:

# content of test_append4.py
import pytest
@pytest.fixture
def first_entry():
    return "a"
@pytest.fixture
def order(first_entry):
    return []
@pytest.fixture(autouse=True)
def append_first(order,first_entry):
    return order.append(first_entry)
def test_string_only(order,first_entry):
    order.append(2)
    assert order==[first_entry,2]

在此示例中,append_first是自动使用fixture。由于是自动发生的,两个侧式都会受到影响,即使两个测试都没有请求它。但并不意味着不能请求它们,只是没有必要。

3.范围:跨类、模组、包或session共享fixtures

fixtures请求网络依赖于连接性,并且通常创建起来很费时。扩展前面的示例,我们可以在@pytest.fixture调用中添加一个scope="module"参数,以导致smtp_connection的fixtures功能,负责创建与预先存在的SMTP服务器的连接,每个测试模块仅调用一次(默认是每个测试函数调用一次)。因此,测试模块中的多个测试函数将各自接收相同的smtp_connection的fixtures实例,从而节省时间。范围的可能值是:函数、类、模块、包或会话。
下一个示例将fixtures函数放入单独的conftest.py文件中,以便目录中多个测试模块的测试可以访问fixtures函数:

# content of conftest.py
import pytest
import smtplib


@pytest.fixture(scope="module")
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
# content of test_module.py


def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert b"smtp.gmail.com" in msg
    assert 0  # for demo purposes


def test_noop(smtp_connection):
    response, msg = smtp_connection.noop()
    assert response == 250
    assert 0  # for demo purposes

这里,test_ehlo需要smtp_connection的fixture值。pytest将发现并调用标记为smtp_connection的fixture函数的@pytest.fixture。

$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

test_module.py FF                                                    [100%]

================================= FAILURES =================================
________________________________ test_ehlo _________________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0001>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b"smtp.gmail.com" in msg
>       assert 0  # for demo purposes
E       assert 0

test_module.py:7: AssertionError
________________________________ test_noop _________________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0001>

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
>       assert 0  # for demo purposes
E       assert 0

test_module.py:13: AssertionError
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo - assert 0
FAILED test_module.py::test_noop - assert 0
============================ 2 failed in 0.12s =============================

你会看到两个assert 0失败,更重要的是可以看到完全相同的smtp_connection对象被传递到两个测试函数中,因为pytest的回溯中显示了传入的参数值。因此,使用smtp_connection的两个测试函数的运行速度与单个函数一样快,因为它们重用了相同的实例。
如果决定拥有一个会话范围的smtp_connection实例,可以简单声明:

@pytest.fixture(scope="session")
def smtp_connection():
    # the returned fixture value will be shared for
    # all tests requesting it
    ...

3.1.fixtures范围

fixtures在测试首次请求时创建,并根据其范围销毁:

  • function:默认作用域,fixture在测试结束时被销毁
  • class:fixture在类中最后一个测试的teardown过程中被销毁
  • module:fixture在模块中最后一个测试的teardown过程中被销毁
  • package:fixture在包中最后一个测试的teardown过程中被销毁
  • session:fixture在测试会话结束时被销毁

注意:pytest一次只缓存一个fixture实例,意味着当使用参数化fixture时,pytest可能会在给定范围内多次调用fixture。

3.2.动态范围(5.2版本出现)

某些情况下,你可能希望在不更改代码的情况下更改fixture的范围。为此,请将可调用对象传递给范围。可调用对象必须返回一个具有有效范围的字符串,并且只会执行一次(在fixture定义期间)。它将使用两个关键字参数调用(fixture_name作为字符串和配置对象的配置)。
这在处理需要时间设置的fixture时特别有用,例如生成docker容器。你可以使用命令行参数来控制不同环境的衍生容器的范围。请参见下面的示例。

def determine_scope(fixture_name, config):
    if config.getoption("--keep-containers", None):
        return "session"
    return "function"


@pytest.fixture(scope=determine_scope)
def docker_container():
    yield spawn_container()
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。