pytest fixture 学习笔记
一篇文章,学透 pytest
核心科技 fixture
。
当前文档适用版本 pytest v6.2.4 doc: https://docs.pytest.org/en/6.2.x/fixture.html#fixture
什么是 fixture
?
从字面意思来看,翻译为 固定装置。 哈?一头雾水,不清楚想表达什么。
按照文档,从源头开始,梳理整个自动化测试过程。 测试是什么?是确保特定行为的结果,和你的预期一致的过程。
这个过程一般化,将分为四个过程:
- Arrange (安排)
- Act (行为)
- Assert (断言)
- Cleanup(清理)
Arrange (安排):开始特定行为前的准备。就像是,你要做吃饭,得先把刀叉筷子准备好了。
Act (行为):启动想要的测试行为,通常采用函数/方法调用的形式
Assert (断言):对行为的结果做出判断
Cleanup(清理):保证其他测试不会受到该测试的影响
测试的核心是 act 和 assert,arrange 和 cleanup 提供上下文。
在 unit 框架中,上下文通过 setup
和 teardown
来实现。在 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"]
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 中共享参数,而不需要显式地传递。
- 参数解析机制:
- 当
pytest.mark.parametrize
标记的参数与 fixture 的参数名称匹配时,pytest 会自动将这些参数传递给fixture
。即使order
fixture 中的参数a
和b
不是通过parametrize
显式传递的,pytest 仍能识别并解析这些参数。
- 当
- 依赖注入:
- 在
test_string_and_int
中,pytest 会先解析a
和b
的值,然后再执行order
fixture。因此,fixture 中的a
和b
可以成功获取到参数值。
- 在
使用 fixture
和 yield
实现测试前置和后置
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
参数可以是 session
、package
、 module
、class
、function
。默认为 function
session
:需结合conftest. py
文件使用,在整个测试会话结束后被销毁package
:在package
中最后一个测试完成后被销毁(package:最近包含__init__.py
文件的所有module
,就是一个package
)module
:在module
中最后一个测试完成后被销毁(模块:一个 py 文件)class
:在class
中最后一个测试完成后被销毁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
。