fixture

一、概述

fixture 是 pytest 框架提供的一个特性,用于管理测试中的资源和设定。它可以在测试函数或测试类中使用,以提供测试所需的固定数据、对象或执行特定的操作。

  • 与 setup 和 teardown 相比,fixture 提供了更灵活、可重用和可扩展的测试环境设置功能。
  • 特点和用法:
    • 灵活性fixture 可以在测试函数、测试类或整个测试模块级别使用,以满足不同的测试需求。
    • 可重用性fixture 可以在多个测试函数或测试类中共享,并可以在不同的测试模块中重复使用。
    • 参数化fixture 可以接受参数,以便在测试时动态生成或配置测试数据和资源。
    • 自动化 :pytest 会自动检测并调用适用的 fixture,无需手动调用。

二、参数列表

python 复制代码
import pytest


@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
    pass
  • scope:可以理解成 fixture 的作用域,默认:function,还有 class、module、package、session 四个。( session 是整个测试会话,即开始执行 pytest 到结束测试。)
  • autouse(默认):False,需要用例手动调用该 fixture;如果是 True,所有作用域内的测试用例都会自动调用该 fixture
  • name(默认):装饰器的名称,同一模块的 fixture 相互调用建议写个不同的 name。

三、通过 fixture 实现 setup 操作

3.1 三种调用方式

  • 方式一:通过名称入参调用
python 复制代码
import pytest


# 1.定义 fixture 。
@pytest.fixture
def login():
    data = {}
    print("\n登录完成!")
    # 返回测试数据或对象给测试函数使用。
    return data


def test_case_01():
    print("无需登录的测试操作。")


# 2.通过 fixture 名称调用。
def test_case_02(login):
    print("需要登录才能进行的测试操作。")


if __name__ == '__main__':
    pytest.main(["-s"])
    # [ 50%]无需登录的测试操作。
    #
    # 登录完成!
    # [100%]需要登录才能进行的测试操作。
  • 方式二:测试用例加上装饰器 @pytest.mark.usefixtures(fixture_name)
python 复制代码
import pytest


@pytest.fixture
def login():
    data = {}
    print("\n登录完成!")
    return data


# 1.再添加一个 fixture。
@pytest.fixture
def authentication():
    print("\n认证完成!")


def test_case_01():
    print("无需登录的测试操作。")


# 2.使用装饰器指定。
@pytest.mark.usefixtures("login", "authentication")
def test_case_02():
    print("需要登录并通过验证,才能进行的测试操作。")


if __name__ == '__main__':
    pytest.main(["-s"])
    # [ 50%]无需登录的测试操作。
    #
    # 登录完成!
    # 认证完成!
    # [100%]需要登录并通过验证,才能进行的测试操作。
  • 方式三:自动调用
python 复制代码
import pytest


# 1.设置自动应用到每个测试函数中。
@pytest.fixture(autouse=True)
def query_database():
    data = {}
    print("\n完成数据库查询!")
    return data


def test_case_01():
    print("执行测试用例一。")


def test_case_02():
    print("执行测试用例二。")


if __name__ == '__main__':
    pytest.main(["-s"])
    # 完成数据库查询!
    # [ 50%]执行测试用例一。
    #
    # 完成数据库查询!
    # [100%]执行测试用例二。

3.2 类声明调用

  • 类声明 上面加 @pytest.mark.usefixtures() ,代表这个类里面所有 测试用例都会调用该 fixture
python 复制代码
import pytest


@pytest.fixture
def query_database():
    print("query_database")


# 在类上面声明。(所有用例都调用。)
@pytest.mark.usefixtures("query_database")
class Tests:
    def test_case_01(self):
        print("test_case_01")

    def test_case_02(self):
        print("test_case_02")


if __name__ == '__main__':
    pytest.main(["-s"])
    # query_database
    # [ 50%]test_case_01
    #
    # query_database
    # [100%]test_case_02

3.3 叠加使用

  • 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层:
python 复制代码
import pytest


@pytest.fixture
def query_database():
    print("query_database")


@pytest.fixture
def authentication():
    print("authentication")


# 叠加使用。(从下到上执行。)
@pytest.mark.usefixtures("query_database")
@pytest.mark.usefixtures("authentication")
def test_case_01():
    print("test_case_01")


if __name__ == '__main__':
    pytest.main(["-s"])
    # authentication
    # query_database
    # [100%]test_case_01

3.4 同时传多个参数

  • @pytest.mark.usefixtures() 可以传多个 fixture 参数,先执行的放前面,后执行的放后面:
python 复制代码
import pytest


@pytest.fixture
def query_database():
    print("query_database")


@pytest.fixture
def authentication():
    print("authentication")


# 传多个参数。(从前到后执行。)
@pytest.mark.usefixtures("authentication", "query_database")
def test_case_01():
    print("test_case_01")


if __name__ == '__main__':
    pytest.main(["-s"])
    # authentication
    # query_database
    # [100%]test_case_01

3.5 获取 fixture 返回值

  • 如果 fixture 有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式
python 复制代码
import json

import pytest


@pytest.fixture
def query_database():
    # mock 返回的数据。
    data = {
        "code": 0,
        "msg": "SUCCESS",
        "user": {
            "name": "jan",
            "age": 18
        }
    }
    print("query_database successes!")
    return json.dumps(data)


# 1.装饰器无法获取返回值。
@pytest.mark.usefixtures("query_database")
def test_case_01():
    print("test_case_01")


# 2.通过名称入参才能获取返回值。
def test_case_02(query_database):
    print("test_case_02 get res=", json.loads(query_database))


if __name__ == '__main__':
    pytest.main(["-s"])
    # test_case_01
    # test_case_02 get res= {'code': 0, 'msg': 'SUCCESS', 'user': {'name': 'jan', 'age': 18}}

3.6 fixture 依赖其他 fixture

如果 fixture 还想依赖其他 fixture,需要用函数传参的方式,不能用 @pytest.mark.usefixtures() 的方式,否则会不生效。

  • 代码示例
python 复制代码
import pytest


@pytest.fixture(scope="session")
def open_browser():
    print("===打开浏览器===")


# 1.依赖其他 fixture 。
@pytest.fixture
# 通过 @pytest.mark.usefixtures 调用不生效。
# @pytest.mark.usefixtures("open_browser")
# 需要通过名称入参。
def query_database(open_browser):
    print("===查询数据库===")


def test_case_01(query_database):
    pass


if __name__ == '__main__':
    pytest.main(["-s"])
    # ===打开浏览器===
    # ===查询数据库===

四、实例化顺序

  • 根据 scope 范围实例化: session > package > module > class > function
  • 具有相同作用域的 fixture 遵循测试函数中声明的顺序,并遵循 fixture 之间的依赖关系。(在 fixture_A 里面依赖的 fixture_B ,则 fixture_B 优先实例化。)
  • 自动使用autouse=True)的 fixture 将在显式使用(传参或装饰器)的 fixture 之前实例化。

五、通过 fixture 实现 teardown 操作

5.1 yield 实现 teardown

fixture 需要搭配 yield 关键字来开启 teardown 操作。

  • yield 关键字用于定义一个生成器函数。

  • 生成器函数是一种特殊的函数,它不会像普通函数一样立即执行并返回一个结果,而是每次迭代 时返回一个值,并在下一个迭代时从上次离开的位置继续执行

  • 通过使用 yield 关键字,生成器函数可以保存其状态,这使得它们在处理大量数据时非常高效。

  • 代码示例

python 复制代码
import pytest


@pytest.fixture(scope="session")
def open_browser():
    print("===打开浏览器===")
    yield
    print("===关闭浏览器===")


@pytest.fixture
def query_database(open_browser):
    print("===开始查询数据库===")
    name = "jan"
    age = 18
    yield name, age
    print("===数据库查询完成===")


def test_case_01(query_database):
    print("===执行测试用例1===")
    # 获取返回结果。
    print("返回:", query_database)
    name, age = query_database
    # 断言。
    assert "jan" == name
    assert 18 == age


def test_case_02(query_database):
    print("===执行测试用例2===")


if __name__ == '__main__':
    pytest.main(["-s"])
    # ===打开浏览器===
    #
    # ===开始查询数据库===
    # PASSED ===执行测试用例1===
    # 返回: ('jan', 18)
    # ===数据库查询完成===
    #
    # ===开始查询数据库===
    # PASSED ===执行测试用例2===
    # ===数据库查询完成===
    #
    # ===关闭浏览器===
  • 如果 yield 前面的代码,即 setup 部分已经抛出异常了,则不会执行 yield 后面的 teardown 内容。
  • 如果测试用例抛出异常,yield 后面的 teardown 内容还是会正常执行。

5.2 yield + with 的结合

  • 官方示例
python 复制代码
@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
        yield smtp_connection  # provide the fixture value
  • smtp_connection() 连接将测试完成执行后已经关闭,因为 smtp_connection() 对象自动关闭 时,with 语句结束。

5.3 addfinalizer 终结函数

  • 代码示例
python 复制代码
import pytest


@pytest.fixture(scope="module")
def open_browser(request):
    print("===打开浏览器===")

    def close_browser():
        print("===关闭浏览器===")

    # addfinalizer 将 close_browser 函数注册为终结器。
    request.addfinalizer(close_browser)


def test_case_01(open_browser):
    print("===执行测试用例1===")


def test_case_02(open_browser):
    print("===执行测试用例2===")


if __name__ == '__main__':
    pytest.main(["-s"])
    # ===打开浏览器===
    # PASSED ===执行测试用例1===
    # PASSED ===执行测试用例2===
    # ===关闭浏览器===
  • 如果 request.addfinalizer() 前面的代码,即 setup 部分已经抛出异常了,则不会执行 request.addfinalizer() 的 teardown 内容。
  • 可以声明多个终结函数并调用。

六、结束语

"-------怕什么真理无穷,进一寸有一寸的欢喜。"

微信公众号搜索:饺子泡牛奶

相关推荐
思则变1 小时前
[Pytest] [Part 2]增加 log功能
开发语言·python·pytest
漫谈网络1 小时前
WebSocket 在前后端的完整使用流程
javascript·python·websocket
try2find3 小时前
安装llama-cpp-python踩坑记
开发语言·python·llama
博观而约取4 小时前
Django ORM 1. 创建模型(Model)
数据库·python·django
精灵vector5 小时前
构建专家级SQL Agent交互
python·aigc·ai编程
Zonda要好好学习6 小时前
Python入门Day2
开发语言·python
Vertira6 小时前
pdf 合并 python实现(已解决)
前端·python·pdf
太凉6 小时前
Python之 sorted() 函数的基本语法
python
项目題供诗6 小时前
黑马python(二十四)
开发语言·python
晓13137 小时前
OpenCV篇——项目(二)OCR文档扫描
人工智能·python·opencv·pycharm·ocr