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

网友投稿 542 2022-05-29

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 = 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 = 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容器。你可以使用命令行参数来控制不同环境的衍生容器的范围。请参见下面的示例。

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

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()

Python

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用IntelliJ IDEA和Maven创建Java项目入门
下一篇:Pytorch笔记搬运:张量
相关文章