Python pytest 之 fixture

前言

  • 虽然 setup 和 teardown 可以执行一些前置和后置操作,但是这种是针对整个脚本全局生效的
  • 如果有以下场景:1.用例一需要执行登录操作;2.用例二不需要执行登录操作;3.用例三需要执行登录操作,则setup和teardown则不满足要求。
  • fixture 可以让自定义测试用例的前置条件

fixture 的优势

  • 命名方式灵活,不限于 setup 和 teardown 两种命名
  • conftest.py 可以实现数据共享,不需要执行 import 就能自动找到 fixture
  • scope=module,可以实现多个 .py 文件共享前置
  • scope=“session” 以实现多个 .py 跨文件使用一个 session 来完成多个用例

fixture 参数

fixture 修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
def fixture(scope="function", params=None, autouse=False, ids=None, name=None)

参数:
scope:被标记方法的作用域,可选四组参数
"function" (default):作用于每个测试方法,每个test都运行一次
"class":作用于整个类,每个class的所有test只运行一次
"module":作用于整个模块,每个module的所有test只运行一次
"session:作用于整个session(慎用),每个session只运行一次,即整个测试会话,即开始执行 Pytest 到结束测试
默认取值为 function(函数级别),控制范围的排序为:`session > module > class > function`
params:(list类型)提供参数数据,供调用标记方法的函数使用
autouse:是否自动运行,默认为False不运行,设置为True自动运行
ids: 每个参数对应的字符串id列表,因此它们是测试id的一部分。如果没有提供id,它们将从参数中自动生成。
name: fixture的名称。 这默认为装饰函数的名称。 如果fixture在定义它的同一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名 “fixture_ <fixturename>”然后使用”@ pytest.fixture(name ='<fixturename>')”。

如何调用 fixture

执行的三种方式:

  1. 将 fixture 名称作为测试用例的输入参数
  2. 使用装饰器 pytest.mark.usefixtures(fixturename)
  3. fixture 设置 autouse=True

扩展知识点:

  • 在类声明上面加 @pytest.mark.usefixtures(),代表这个类里面所有测试用例都会调用该 fixture
  • 可以叠加多个 @pytest.mark.usefixtures(),先执行的放底层,后执行的放上层
  • 可以传多个 fixture 参数,先执行的放前面,后执行的放后面
  • 如果 fixture 有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式一)

将fixture名称作为测试用例的输入参数

1
2
3
4
5
6
7
8
9
class Test_ABC:
@pytest.fixture()
def before(self):
print("------->before")
def test_a(self,before): # ️ test_a方法传入了被fixture标识的函数,以参数的形式
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
1
2
3
4
执行结果:
test_abc.py
------->before # 发现before会优先于测试函数运行
------->test_a

使用装饰器 pytest.mark.usefixtures(fixturename)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import pytest


@pytest.fixture() # fixture标记的函数可以应用于测试类外部
def before():
print("------->before")


@pytest.mark.usefixtures("before")
class TestOne:
def setup(self):
print("------->setup")

def test_a(self):
print("------->test_a")
assert 1

def test_b(self):
print("------->test_b")
assert 1


if __name__ == '__main__':
pytest.main("-s test_one.py")
1
2
3
4
5
6
7
8
9
10
执行结果:
test_one.py::TestOne::test_a
------->before # 发现before自动优先于setup运行
------->setup
------->test_a
PASSED
test_one.py::TestOne::test_b ------->before
------->setup
------->test_b
PASSED

fixture设置 autouse=True

1
2
3
4
5
6
7
8
9
10
11
12
import pytest
@pytest.fixture(autouse=True) # 设置为默认运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
1
2
3
4
5
执行结果:
test_abc.py
------->before # 发现before自动优先于测试类运行
------->setup
------->test_a

fixture的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
import pytest
@pytest.fixture()
def need_data():
return 2 # 返回数字2

class Test_ABC:

def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 拿到返回值做一次断言

if __name__ == '__main__':
pytest.main("-s test_abc.py")
1
2
3
4
执行结果:
test_abc.py
------->test_a
.
1
2
3
4
5
6
7
8
9
10
11
12
import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request 系统封装参数
return request.param # 取列表中单个值,默认的取值方式
class Test_ABC:

def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 断言need_data不等于3

if __name__ == '__main__':
pytest.main("-s test_abc.py")
1
2
3
4
5
6
7
8
9
10
11
12
执行结果:
# 可以发现结果运行了三次
test_abc.py
1
------->test_a
.
2
------->test_a
.
3
------->test_a
F

fixture 使用 yield 实现teardown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import pytest


@pytest.fixture(scope="session")
def open():
# 会话前置操作setup
print("===打开浏览器===")
test = "test"
yield test
# 会话后置操作teardown
print("==关闭浏览器==")


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


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


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


if __name__ == '__main__':
pytest.main("-s test_one.py")
1
2
3
4
5
6
7
8
9
10
11
12
13
执行结果:
test_one.py::test_s1 ===打开浏览器===
open方法yield的返回结果:test
==用例1==
('==我是账号==', '==我是密码==')
==我是账号== ==我是密码==
PASSED登录成功

test_one.py::test_s2 open方法yield的返回结果:test
==用例2==
('==我是账号==', '==我是密码==')
PASSED登录成功
==关闭浏览器==

yield和with结合使用

1
2
3
4
5
# 官方例子
@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 语句结束

fixtrue 参数 request

传单个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pytest


@pytest.fixture()
def pre_data(request):
name = request.param
print(f"== 账号是:{name} ==")
return name


data = ["pyy1", "polo"]
ids = [f"pre_test_name is:{name}" for name in data]

#添加indirect=True参数是为了把 login 当成一个函数去执行,而不是一个参数,并且将 data 当做参数传入函数
#def test_name(login) ,这里的 login 是获取 fixture 返回的值
@pytest.mark.parametrize("pre_data", data, ids=ids, indirect=True)
def test_name(pre_data):
print(f" 测试用例的登录账号是:{pre_data} ")

多个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@pytest.fixture()
def pre_data(request):
param = request.param
print(f"账号是:{param['username']},密码是:{param['pwd']}")
return param


data = [
{"username": "name1", "pwd": "pwd1"},
{"username": "name2", "pwd": "pwd2"},
]

#如果需要传多个参数,需要通过字典去传
@pytest.mark.parametrize("pre_data", data, indirect=True)
def test_name_pwd(pre_data):
print(f"账号是:{pre_data['username']},密码是:{pre_data['pwd']}")

多个fixture(只加一个装饰器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 多个fixture
@pytest.fixture(scope="module")
def input_user(request):
user = request.param
print("登录账户:%s" % user)
return user


@pytest.fixture(scope="module")
def input_psw(request):
psw = request.param
print("登录密码:%s" % psw)
return psw


data = [
("name1", "pwd1"),
("name2", "pwd2")
]


@pytest.mark.parametrize("input_user,input_psw", data, indirect=True)
def test_more_fixture(input_user, input_psw):
print("fixture返回的内容:", input_user, input_psw)

多个fixture(叠加装饰器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 多个fixture
@pytest.fixture(scope="function")
def input_user(request):
user = request.param
print("登录账户:%s" % user)
return user


@pytest.fixture(scope="function")
def input_psw(request):
psw = request.param
print("登录密码:%s" % psw)
return psw


name = ["name1", "name2"]
pwd = ["pwd1", "pwd2"]


@pytest.mark.parametrize("input_user", name, indirect=True)
@pytest.mark.parametrize("input_psw", pwd, indirect=True)
def test_more_fixture(input_user, input_psw):
print("fixture返回的内容:", input_user, input_psw)

Python pytest 之 fixture
https://flepeng.github.io/021-Python-34-框架-pytest-Python-pytest-之-fixture/
作者
Lepeng
发布于
2021年4月27日
许可协议