pytest fixture 学习笔记

一篇文章,学透 pytest 核心科技 fixture

当前文档适用版本 pytest v6.2.4 doc: https://docs.pytest.org/en/6.2.x/fixture.html#fixture

什么是 fixture?

从字面意思来看,翻译为 固定装置。 哈?一头雾水,不清楚想表达什么。

按照文档,从源头开始,梳理整个自动化测试过程。 测试是什么?是确保特定行为的结果,和你的预期一致的过程。

这个过程一般化,将分为四个过程:

  1. Arrange (安排)
  2. Act (行为)
  3. Assert (断言)
  4. Cleanup(清理)

Arrange (安排):开始特定行为前的准备。就像是,你要做吃饭,得先把刀叉筷子准备好了。

Act (行为):启动想要的测试行为,通常采用函数/方法调用的形式

Assert (断言):对行为的结果做出判断

Cleanup(清理):保证其他测试不会受到该测试的影响

测试的核心是 actassertarrangecleanup 提供上下文。

在 unit 框架中,上下文通过 setupteardown 来实现。在 pytest 框架中,更一般化地考虑,act 过程中,也会需要使用到某些上下文,因此需要这个上下文信息可以在测试过程中被灵活地调用。这个被调用的东西,就抽象叫做 fixture。 一个固定装置,可以放置在任何地方。当上下文比较复杂时候,可以拆解成多个解耦的,独立的 fixture,进行灵活搭配和服用,发挥强大的能力。(这是我的个人理解,待考证)

fixture 是 pytest 组织测试的方式,在写一个测试的时候,你需要知道你需要使用哪些 fixture,它们如何组织起来。

fixture 是怎么 work 的?

测试函数通过将 fixture 作为形式参数来请求来使用 fixture

 1import pytest
 2
 3class Fruit:
 4    def __init__(self, name):
 5        self.name = name
 6
 7    def __eq__(self, other):
 8        return self.name == other.name
 9
10@pytest.fixture
11def my_fruit():
12    return Fruit("apple")
13
14@pytest.fixture
15def fruit_basket(my_fruit):
16    return [Fruit("banana"), my_fruit]
17
18def test_my_fruit_in_basket(my_fruit, fruit_basket):
19    assert my_fruit in fruit_basket
1INFO     root:test_learn_fixture.py:25 my_fruit: apple
2INFO     root:test_learn_fixture.py:26 fruit_basket: ['banana', 'apple']
3PASSED

fixture 可以进行灵活组合使用

 1import pytest  
 2  
 3# Arrange  
 4@pytest.fixture  
 5def first_entry():  
 6    return "a"  
 7  
 8# Arrange  
 9@pytest.fixture  
10def order(first_entry):  
11    return [first_entry]  
12  
13def test_string(order):  
14    # Act  
15    order.append("b")  
16    logging.info(f"order: {order}")  
17  
18    # Assert  
19    assert order == ["a", "b"]
1INFO     root:test_learn_fixture.py:30 order: ['a', 'b']
2PASSED 

fixture 可以被重复使用

 1import pytest
 2
 3# Arrange
 4@pytest.fixture
 5def first_entry():
 6    return "a"
 7
 8# Arrange
 9@pytest.fixture
10def order(first_entry):
11    return [first_entry]
12
13def test_string(order):
14    # Act
15    order.append("b")
16
17    # Assert
18    assert order == ["a", "b"]
19
20def test_int(order):
21    # Act
22    order.append(2)
23
24    # Assert
25    assert order == ["a", 2]

note:这里的每个测试都被赋予了该 list 对象的自己的副本,这意味着 order 固定装置被执行两次。

一个 test / fixture 可以一次使用多个 fixture

 1import pytest
 2
 3
 4# Arrange
 5@pytest.fixture
 6def first_entry():
 7    return "a"
 8
 9
10# Arrange
11@pytest.fixture
12def second_entry():
13    return 2
14
15
16# Arrange
17@pytest.fixture
18def order(first_entry, second_entry):
19    return [first_entry, second_entry]
20
21
22# Arrange
23@pytest.fixture
24def expected_list():
25    return ["a", 2, 3.0]
26
27
28def test_string(order, expected_list):
29    # Act
30    order.append(3.0)
31
32    # Assert
33    assert order == expected_list

使用 autouse=True 来自动使用 fixture

 1def order():  
 2    return 1  
 3  
 4@pytest.fixture(autouse=True)  
 5def use_automated_fixture(order, first_entry):  
 6    logging.info("所有函数开始之前执行...")  
 7    yield   
 8    logging.info("所有函数结束之后执行...")  
 9  
10  
11def test_string_and_int(order):  
12    assert order == 1
1-------------------------------- live log setup --------------------------------
2INFO     root:test_learn_fixture.py:13 所有函数开始之前执行...
3PASSED                                                                   [100%]
4------------------------------ live log teardown -------------------------------
5INFO     root:test_learn_fixture.py:15 所有函数结束之后执行...

Note:这里使用 yield ,分隔每个 test 的前置和后置操作。

fixture 如何使用参数化参数(测试函数和 fixture 共享参数)

 1import pytest  
 2  
 3@pytest.fixture  
 4def order(a, b):  
 5    return a * b  
 6  
 7@pytest.mark.parametrize("a", [1, 2, 3])  
 8@pytest.mark.parametrize("b", [2, 4, 6])  
 9def test_string_and_int(a, b, order):  
10    logging.info(f"order is {order}")  
11    assert order == a * b

这是 pytest 的一个功能,它可以在测试函数和 fixture 中共享参数,而不需要显式地传递。

  1. 参数解析机制:
    • pytest.mark.parametrize 标记的参数与 fixture 的参数名称匹配时,pytest 会自动将这些参数传递给 fixture。即使 order fixture 中的参数 ab 不是通过 parametrize 显式传递的,pytest 仍能识别并解析这些参数。
  2. 依赖注入:
    • test_string_and_int 中,pytest 会先解析 ab 的值,然后再执行 order fixture。因此,fixture 中的 ab 可以成功获取到参数值。

使用 fixtureyield 实现测试前置和后置

 1```python
 2def order():  
 3    return 1  
 4  
 5@pytest.fixture(autouse=True)  
 6def use_automated_fixture(order, first_entry):  
 7    logging.info("所有函数开始之前执行...")  
 8    yield   
 9    logging.info("所有函数结束之后执行...")  
10  
11  
12def test_string_and_int(order):  
13    assert order == 1

fixture 的工作范围

fixture 在测试首次请求时创建,并根据其 scope 销毁:

scope 参数可以是 sessionpackagemoduleclassfunction。默认为 function

  1. session:需结合 conftest. py 文件使用,在整个测试会话结束后被销毁
  2. package:在 package 中最后一个测试完成后被销毁(package:最近包含 __init__.py 文件的所有 module,就是一个 package
  3. module:在 module 中最后一个测试完成后被销毁(模块:一个 py 文件)
  4. class:在 class 中最后一个测试完成后被销毁
  5. function:默认。测试结束时 fixture 立马被销毁

Note: Pytest only caches one instance of a fixture at a time, which means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope.
Pytest 一次仅缓存一个 fixture 的一个实例,这意味着当使用参数化固定装置时,pytest 可能会在给定范围内多次调用 fixture