[跟着官方文档学pytest][五][学习笔记]
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()
- 点赞
- 收藏
- 关注作者
评论(0)