pytest系列教程——4、fixture详解

2022-06-08 15:44:33 浏览数 (1)

1、fixture简绍

官方解释如下

Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a fixture function. The name of the fixture function can later be referenced to cause its invocation ahead of running tests: test modules or classes can use the pytest.mark.usefixtures(fixturename) marker. Test functions can directly use fixture names as input arguments in which case the fixture instance returned from the fixture function will be injected. Fixtures can provide their values to test functions using return or yield statements. When using yield the code block after the yield statement is executed as teardown code regardless of the test outcome, and must yield exactly once.

https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.fixture

fixture属于pytest中的一个方法。fixture可以用作测试用例的前置和后置操作,其中fixture命令规范没有像setup和teardown固定格式。可以随意命名。控制fixture的前置和后置操作是通过yield关键字进行来区分的,代码在yield前面的属于前置操作,代码在yield后面的属于后置操作。并且fixture也没有强烈的要求必须要前后置同时存在,可以只存在前置也可以只存在后置。fixture如果有后置内容,无论遇到什么问题,都会进行执行后置的代码。

2、fixture基本用法

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture
def login():
    print("login:用户执行登录操作")


# 使用夹具函数的测试用例
def test_01(login):
    print("test_01")


# 使用夹具函数的测试用例
def test_02(login):
    print("test_02")

    
# 不使用夹具函数的测试用例
def test_03():
    print("test_03")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

执行结果:

代码语言:javascript复制
collected 3 items

MyPytest.py login:用户执行登录操作
test_01
.login:用户执行登录操作
test_02
.test_03
.
============================== 3 passed in 0.07s ==============================

fixture通过@pytest.fixture()装饰器装饰login()函数,那么这个函数就是一个fixture。上述代码中,声明的测试夹具函数login,作为参数传入其他函数(不需要带括号哦),即可完成调用。可以传多个fixture,按先后顺序执行。

3、调用fixture的方式

a、Fixture名字作为测试用例的参数

即上述案例中的调用方式,将定义的fixture作为测试用例的参数传递进去:

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture
def login():
    print("login:用户执行登录操作")


# 使用夹具函数的测试用例
def test_01(login):
    print("test_01")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])
b、使用@pytest.mark.usefixtures('fixture')装饰器

使用@pytest.mark.usefixtures('fixture')的方式调用,效果同上:

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture
def login():
    print("login:用户执行登录操作")


# 使用夹具函数的测试用例
@pytest.mark.usefixtures('login')
def test_01():
    print("test_01")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])
c、使用autouse参数

@pytest.fixture()的参数中的字段autouse = True的情况下,默认会在模块内的每条用例调用时候自动用到:

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture(autouse=True)
def login():
    print("login:用户执行登录操作")


# 自动使用夹具函数的测试用例
def test_01():
    print("test_01")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

4、fixture函数参数

image.png

@pytest.fixture(scope = "function",params=None,autouse=False,ids=None,name=None)

scope

控制fixture的作用范围,也就是哪些用例需要调用这个fixture。有点类似之前讲过的setup_module 、setup_class等等。

取值

作用

function

函数级 每一个函数或方法都会调用

class

函数级 模块级 每一个.py文件调用一次

module

模块级 每一个.py文件调用一次

session

会话级 每次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法

session,我后面在conftest.py的用法时候讲解。

举个例子说明:

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture(scope="class")
def login():
    print("login:用户执行登录操作")


# 当fixtrue的scope="class",则只在该类中执行一次
class Test_Fixture():
    def test_02(self, login):
        print("test_02")

    def test_03(self, login):
        print("test_03")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])
代码语言:javascript复制
collected 2 items

MyPytest.py login:用户执行登录操作
test_02
.test_03
.

============================== 2 passed in 0.07s ==============================

***Repl Closed***
params

Fixture的可选形参列表,支持列表传入,默认None,每个param的值,fixture都会去调用执行一次,类似for循环,可与参数ids一起使用,作为每个参数的标识,详见ids。被Fixture装饰的函数要调用采用:request.param(固定写法)

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture(params=["胡八万","胡三条","胡七筒"])
def login(request):
    temp = request.param
    print(f"login:用户{temp}执行登录操作")

    return temp


#当fixture 的params参数 会被循环执行
def test_01(login):
    print(f"{login}已经登录成功~")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])
代码语言:javascript复制
collected 3 items

MyPytest.py login:用户胡八万执行登录操作
胡八万已经登录成功~
.login:用户胡三条执行登录操作
胡三条已经登录成功~
.login:用户胡七筒执行登录操作
胡七筒已经登录成功~
.

============================== 3 passed in 0.07s ==============================

***Repl Closed***
ids

用例标识ID,与params配合使用,一对一关系

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture(params=["胡八万", "胡三条", "胡七筒"],ids=['用例A','用例B','用例C'])
def login(request):
    temp = request.param
    print(f"login:用户{temp}执行登录操作")

    return temp


# 当fixture 的params参数 会被循环执行
def test_01(login):
    print(f"{login}已经登录成功~")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

有的编辑器执行无效果,但是使用pycharm就可以看出用例的编号

autouse

默认False,若为True,每个测试函数都会自动调用该fixture,无需传入fixture函数名。参考之前的代码案例。

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture(autouse=True)
def login():
    print("login:用户执行登录操作")


# 自动使用夹具函数的测试用例
def test_01():
    print("test_01")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])
name

fixture的重命名。重新把它命名一下,之前的函数名将不生效

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture(name='new_login')
def login(request):
    print(f"login:用户执行登录操作")


def test_01(new_login):
    print("test_01已经登录成功~")


def test_02(login):
    print("test_02已经登录成功~")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

执行上述代码将报错,提示fixture 'login' not found

5、fixture对比setup、teardown

  • 全局共享conftest.py,可以统一管理、多处调用和自动调用
  • 命名方式灵活,不局限于setup和teardown这个几个命名
  • fixture出现重名,就近原则
  • conftest.py为fixture而生的,可以方便管理、修改和查看fixture函数
  • 自动引入不需要导入

6、fixture配合yield 实现teardown

关于yield可以看我之前写的一篇专门介绍它基本用法的文章。这里不再赘述 yeild也是一种函数的返回值类型,是函数上下文管理器,使用yield被调fixture函数执行遇到yield会停止执行,接着执行调用的函数,调用的函数执行完后会继续执行fixture函数yield关键后面的代码。因此利用fixture函数,我们可以说pytest集合了setup、teardown,既做了初始化,又做了后置的清理工作。

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture()
def login(request):
    print("login:用户执行登录操作")
    yield
    print("退出登录~")


def test_01(login):
    print("test_01已经登录成功~")


def test_02(login):
    print("test_02已经登录成功~")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

执行结果:

代码语言:javascript复制
collected 2 items

MyPytest.py login:用户执行登录操作
test_01已经登录成功~
.退出登录~
login:用户执行登录操作
test_02已经登录成功~
.退出登录~

============================== 2 passed in 0.07s ==============================

***Repl Closed***

7、addfinalizer 终结函数

addfinalizer,终结函数。效果上是大致相同的,但是在用法上,addfinalizer跟yield是不同的,需要注册作为终结器使用的函数。

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture()
def login(request):
    print("login:用户执行登录操作")

    def logout():
        print("退出登录~")

    # 注册logout为终结函数
    request.addfinalizer(logout)


def test_01(login):
    print("test_01已经登录成功~")


def test_02(login):
    print("test_02已经登录成功~")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

执行结果:

代码语言:javascript复制
collected 2 items

MyPytest.py login:用户执行登录操作
test_01已经登录成功~
.退出登录~
login:用户执行登录操作
test_02已经登录成功~
.退出登录~

============================== 2 passed in 0.07s ==============================

***Repl Closed***

addfinalizer可以定义多个终结函数,但是执行顺序是定义时候顺序的倒叙:

代码语言:javascript复制
import pytest


# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture()
def login(request):
    print("login:用户执行登录操作")

    def logout01():
        print("退出登录01~")
    def logout02():
        print("退出登录02~")
    def logout03():
        print("退出登录03~")

    # 注册logout为终结函数
    request.addfinalizer(logout01)
    request.addfinalizer(logout02)
    request.addfinalizer(logout03)


def test_01(login):
    print("test_01已经登录成功~")


def test_02(login):
    print("test_02已经登录成功~")


if __name__ == '__main__':
    pytest.main(['MyPytest.py', '-s'])

运行结果:

代码语言:javascript复制
collected 2 items

MyPytest.py login:用户执行登录操作
test_01已经登录成功~
.退出登录03~
退出登录02~
退出登录01~
login:用户执行登录操作
test_02已经登录成功~
.退出登录03~
退出登录02~
退出登录01~


============================== 2 passed in 0.07s ==============================

***Repl Closed***

好了,今天的内容比较多,因为pytest的精髓也在fixture。所以希望本文的案例大家跟着大家敲一遍,纸上得来终觉浅~

0 人点赞