如果有以下场景:用例 1 需要先登录,用例 2 不需要登录,用例 3 需要先登录。很显然无法用 setup 和 teardown 来实现了
fixture 可以让我们自定义测试用例的前置条件

1.fixture 的优势

命名方式灵活,不局限于 setup 和 teardown 这几个命名
conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到 fixture
scope 的取值:
function:每一个函数或方法都会调用
class:每一个类调用一次,一个类可以有多个方法
module:每一个.py 文件调用一次,该文件内又有多个 function 和 class
session:是多个文件调用一次,可以跨.py 文件调用,每个.py 文件就是 module

2.fixture 参数列表

@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
    print("fixture初始化的参数列表")

scope:可以理解成 fixture 的作用域,默认:function,还有 class、module、package、session 四个【常用】
autouse:默认:False,需要用例手动调用该 fixture;如果是 True,所有作用域内的测试用例都会自动调用该 fixture
name:默认:装饰器的名称,同一模块的 fixture 相互调用建议写个不同的 name
注意 session 的作用域:是整个测试会话,即开始执行 pytest 到结束测试

3.测试用例如何调用 fixture?

import pytest

# 调用方式一:将fixture名称作为测试用例函数的输入参数
@pytest.fixture
def login():
    print("输入账号,密码先登录")


def test_s1(login):
    print("用例 1:登录之后其它动作 111")

>>>====auto===
>>>输出账号密码先登录
>>>用例 1登录之后其它动作 111

def test_s2():  # 不传 login
    print("用例 2:不需要登录,操作 222")

>>>====auto===
>>>用例 2不需要登录操作 222

# 调用方式二:测试用例加上装饰器:@pytest.mark.usefixtures(fixture_name)
@pytest.fixture
def login2():
    print("please输入账号,密码先登录")


@user6res("login2", "login")
def test_s11():
    print("用例 11:登录之后其它动作 111")

>>>====auto===
>>>please输入账号密码先登录
>>>输入账号密码先登录
>>>用例 11登录之后其它动作 111

# 调用方式三:fixture设置autouse=True,有作用域内的测试用例都会自动调用该fixture
@pytest.fixture(autouse=True)
def login3():
    print("====auto===")


# 不是test开头,加了装饰器也不会执行fixture
@user8res("login2")
def loginss():
    print(123)

Point!!!

4.fixture 的实例化顺序

import pytest

order = []

@pytest.fixture(scope="session")
def s1():
    order.append("s1")


@pytest.fixture(scope="module")
def m1():
    order.append("m1")


@pytest.fixture
def f1(f3, a1):
    # 先实例化f3, 再实例化a1, 最后实例化f1
    order.append("f1")
    assert f3 == 123


@pytest.fixture
def f3():
    order.append("f3")
    a = 123
    yield a


@pytest.fixture
def a1():
    order.append("a1")


@pytest.fixture
def f2():
    order.append("f2")


def test_order(f1, m1, f2, s1):
    # m1、s1在f1后,但因为scope范围大,所以会优先实例化
    assert order == ["s1", "m1", "f3", "a1", "f1", "f2"]

首先看有没有 autouse=True,然后 f1,m1,f2,s1 中 scope 最大的是 s1,然后是 m1,所以第一个是” s1“, 第二个是” m1“,然后其他的 scope 相同,先看 f1,将 f1 依赖的 f3 先实例化,所以接下来是” f3“,然后是” a1“,然后回到 f1,order 新增” f1“,最后是” f2“
值得注意的是,f3 中用到了 yield,yield 首先是一个 return,将 123return 后在 f1 中断言成功

5.关于 fixture 的注意点

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

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

@pytest.fixture
# @pytest.mark.usefixtures("open") 不可取!!!不生效!!!
def login(open):
    # 方法级别前置操作setup
    print(f"输入账号,密码先登录{open}")

6.fixture 之 yield 实现 teardown

用 fixture 实现 teardown 并不是一个独立的函数,而是用 yield 关键字来开启 teardown 操作

@pytest.fixture(scope="session")
def open_browser():
    # 会话前置操作setup
    print("===打开浏览器===")
    test = "测试变量是否返回"
    yield test
    # 会话后置操作teardown
    print("==关闭浏览器==")


@pytest.fixture
def login(open_browser):
    # 方法级别前置操作setup
    print(f"输入账号,密码先登录{open_browser}")
    name = "==我是账号=="
    pwd = "==我是密码=="
    age = "==我是年龄=="
    # 返回变量
    yield name, pwd, age
    # 方法级别后置操作teardown
    print("登录成功")


def test_s1(login):
    print("==用例1==")
    # 返回的是一个元组
    print(login)
    # 分别赋值给不同变量
    name, pwd, age = login
    print(name, pwd, age)
    assert "账号" in name
    assert "密码" in pwd
    assert "年龄" in age


def test_s2(login):
    print("==用例2==")
    print(login)

如果 yield 前面的代码,即 setup 部分已经抛出异常了,则不会执行 yield 后面的 teardown 内容
如果测试用例抛出异常,yield 后面的 teardown 内容还是会正常执行

7.官方案例之 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

上下文管理器:
有上下文管理器前:

f = open("test.txt")
try:
    for line in f.readlines():
        print(line)
finally:
    f.close()

有上下文管理器后:

with expression [as variable]:  # 如果指定了as variable说明符,则variable是上下文管理器expression调用__enter__()函数返回的对象。所以,f并不一定就是expression,而是expression.__enter__()的返回值;with-block是执行语句,with-block执行完毕时,with语句会自动进行资源清理.
    with-block
with open("text.txt") as f:
    for line in f.readlines()
    print(line)

所以上面 smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] ),创建 SMTP 对象,执行完模块中的用例后,则返回一个 SMTP 对象,可以发送邮件以及完成一些资源清理操作。


↙↙↙阅读原文可查看相关链接,并与作者交流