likes
comments
collection
share

fixture

作者站长头像
站长
· 阅读数 1

一、概述

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

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

二、参数列表

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 三种调用方式

  • 方式一:通过名称入参调用
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)
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%]需要登录并通过验证,才能进行的测试操作。

  • 方式三:自动调用
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
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() ,先执行的放底层,后执行的放上层:
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 参数,先执行的放前面,后执行的放后面:
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() 是无法获取到返回值的,必须用传参的方式
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() 的方式,否则会不生效。

  • 代码示例
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 关键字,生成器函数可以保存其状态,这使得它们在处理大量数据时非常高效。

  • 代码示例

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 的结合

  • 官方示例
@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 终结函数

  • 代码示例
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 内容。
  • 可以声明多个终结函数并调用。

六、结束语

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

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