pytest之fixtures的运用
fixture的基础运用
setup、teardown 可以实现在执行用例前或结束后加入一些操作,但这种都是针对整个脚本全局的。那么假设我们有模块a、模块b,他们都依赖登录接口,难道我们需要每个模块中,都编写setup、teardown 来实现吗?这样就会导致我们出现非常多冗余的代码。
fixture 的作用是可以让我们自定义测试用例的前置条件。
fixture 的优势
- 命名方式灵活,不局限于 setup 和 teardown 这几个命名
- conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到 fixture
- scope=“module” 可以实现多个 .py 跨文件共享前置
- scope=“session” 可以实现多个 .py 跨文件使用一个 session 来完成多个用例
fixture的参数列表:
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
print("fixture初始化的参数列表")
-
scope:可以理解成 fixture 的作用范围,默认:function,还有 class、module、package、session
- function 的作用域:每一个函数或方法都会调用 - class 的作用域:每一个类调用一次,一个类中可以有多个方法 - module 的作用域:每一个 .py 文件调用一次,该文件内又有多个 function 和 class - session 的作用域:是多个文件调用一次,可以跨 .py 文件调用每个 .py 文件就是 module
-
params:一个可选的参数列表,它将导致多个参数调用 fixture 功能和所有测试使用它
-
autouse:默认:False,需要用例手动调用该 fixture;如果是 True,所有作用域内的测试用例都会自动调用该 fixture
-
ids:每个字符串 id 的列表,每个字符串对应于 params,这样他们就是测试ID的一部分。如果没有提供ID,它们将从 params 自动生成
-
name:默认:装饰器的名称,同一模块的 fixture 相互调用建议写不同的名称
- 创建conftest.py文件
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/24 17:01
# @Author : 余少琪
import pytest
@pytest.fixture(scope="module", autouse=True)
def login_init():
"""
获取token信息
:return:
"""
# 这里是封装的登录的方法
token = "这里是获取token的方法"
return token
- 创建test_demo.py文件
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/18 23:04
# @Author : 余少琪
import pytest
def test_get_user_info(login_init):
"""
:login_init: 在conftest文件封装的获取token信息的函数
:return:
"""
print("这里是获取用户信息的接口", login_init)
if __name__ == '__main__':
pytest.main(['-s'])
下面我们来设计多个使用的场景
import pytest
# 调用方式一
@pytest.fixture
def login():
print("输入账号 login,密码登录")
def test_s1(login):
print("用例 1:登录之后其它动作 test_s1")
def test_s2():
print("用例 2:不需要登录,操作 test_s2")
# 调用方式二
@pytest.fixture
def login2():
print("输入账号 login2,密码登录")
@pytest.mark.usefixtures("login2", "login")
def test_s3():
print("用例 3:登录之后其它动作 test_s3")
# 调用方式三
@pytest.fixture(autouse=True)
def login3():
print("====login3====")
# 不是test开头,加了装饰器也不会执行fixture
@pytest.mark.usefixtures("login2")
def login4():
print("====login4====")
运行结果:
(1)login3 设置为 autouse=True,则每次执行用例前都会先执行 login3
(2)test_s1 引用了入参 login,则先执行 login,之后再执行 test_s1
(3)test_s2 没有引用入参,直接执行 test_s2
(4)test_s3 使用了装饰器,则先执行 login2,之后 login,最后 test_s3
(5)login4 加了装饰器,但不是 test 开头,所以不会执行
关键点
- 在类声明上面加 @pytest.mark.usefixtures() ,代表这个类里面所有测试用例都会调用该 fixture
- 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
- 可以传多个 fixture 参数,先执行的放前面,后执行的放后面
- 如果 fixture 有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式一)
fixture传递测试数据
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/18 23:04
# @Author : 余少琪
import pytest
@pytest.fixture()
def fixturefun():
return (1, 2, 3, 4)
def test_one(fixturefun):
assert fixturefun[0] == 2
def test_two(fixturefun):
assert fixturefun[1] == 2
函数 test_one 断言失败,fixturefun[0] 值是1,fixturefun[1] 的值才是2。
函数 test_two 断言成功。
fixture的实例化顺序
- fixture 的 scope 实例化优先级:session > package > module > class > function。即较高 scope 范围的 fixture(session)在较低 scope 范围的 fixture( function 、 class )之前实例化。
- 具有相同作用域的 fixture 遵循测试函数中声明的顺序,并遵循 fixture 之间的依赖关系。在 fixture_A 里面依赖的 fixture_B 优先实例化,然后到 fixture_A 实例化。
- 自动使用(autouse=True)的 fixture 将在显式使用(传参或装饰器)的 fixture 之前实例化。
import pytest
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="package")
def p1():
order.append("p1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture(scope="class")
def c1():
order.append("c1")
@pytest.fixture(scope="function")
def f0():
order.append("f0")
@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, c1, m1, f0, f2, s1, p1):
# 按scope的优先级,按顺序执行s1,p1,m1,c1,f1(优先执行f3,之后a1,最后f1),f0,f2
assert order == ["s1", "p1", "m1", "c1", "f3", "a1", "f1", "f0", "f2"]
按 scope 的优先级,按顺序执行 s1,p1,m1,c1,f1(优先执行f3,之后a1,最后f1),f0,f2
使用多个fixture
import pytest
@pytest.fixture()
def fixture_one():
print("====fixture_one====")
@pytest.fixture()
def fixture_two():
print("====fixture_two====")
def test_case(fixture_two, fixture_one):
print("====执行用例====")
test_case函数执行前按顺序先执行fixture_two,之后执行fixture_one。
fixture依赖其他fixture
添加了 @pytest.fixture,如果 fixture 还想依赖其他 fixture,需要用函数传参的方式,不能用 @pytest.mark.usefixtures() 的方式,否则会不生效。
import pytest
@pytest.fixture(scope="session")
def open():
print("===打开浏览器open===")
@pytest.fixture
# @pytest.mark.usefixtures("open") 不可取、不生效
def login(open):
# 方法级别前置操作setup
print("===登陆操作login===")
def test_case(login):
print("===执行用例test_case===")
执行用例test_case,会先执行login,但login里会先执行open
所以执行顺序是open->login->test_case
fixture重命名
fixture 允许使用 @pytest.fixture 的 name 参数对 fixture 重命名。
import pytest
@pytest.fixture(name="AllTests")
def fixturefun():
print("====fixturefun====")
@pytest.mark.usefixtures("AllTests")
def test_case():
print("====执行用例====")
fixture之request
为请求对象提供对 request 测试上下文的访问权,并且在 fixture 被间接参数化的情况下具有可选的“param”属性。
参数:
- fixturename = None: 正在执行此 request 的 Fixtures。
- scope = None: 范围字符串,“方法”,“类”,“模块”,“会话”之一。
- fixturenames: 此 request 中所有活动 Fixture 方法的名称。
- node: 底层集合节点(取决于当前 request 范围)。
- config: 与此 request 关联的 pytest 配置对象。
- function: 如果 request 具有按方法范围,则测试函数对象。
- cls: 收集测试函数的 class(可以是None)。
- instance: 收集测试函数的实例(可以是None)。
- module: 收集测试函数的 python 模块对象。
- fspath: 收集此测试的测试模块的文件系统路径。
- keywords: 底层节点的关键字/标记字典。
- session: pytest 会话对象。
- addfinalizer(finalizer): 在 request 测试上下文完成执行的最后一次测试之后添加要调用的终结器/拆卸函数。
- applymarker(marker): 将标记应用于单个测试函数调用。如果你不希望在所有函数调用中都有关键字/标记,则此方法很有用。
- 创建test_fixture_request.py文件
fixture函数可以通过接受request对象来反向获取请求中的测试函数、类或模块上下文。
request.module属性从测试模块中获取smtpserver值。
import pytest
import smtplib
@pytest.fixture(scope="module")
def my_smtp(request):
server = getattr(request.module, "smtpserver", "smtp.163.com")
print("\nfixture 获取到的server :%s" %server)
smtp = smtplib.SMTP(server, 587, timeout=5)
yield smtp
print("\n执行完毕 %s (%s)" % (smtp, server))
smtp.close()
smtpserver = "mail.python.org"
def test_smtp(my_smtp):
print("\n执行测试")
打开命令行,执行命令
pytest -s test_fixture_request.py
运行结果:
获取到smtpserver值mail.python.org
如果将脚本里的smtpserver = "mail.python.org"这句禁用,再次执行后,会用默认值smtp.163.com
request.config.rootdir
request.config.rootdir 获取项目的根目录地址,在fixture_chapter目录下创建data.yaml文件
文件内容:
username: admin
password: 123456
import pytest
import yaml
import os
@pytest.fixture(scope="session", autouse=True)
def datainfo(request):
print("\n项目根目录路径:%s" % request.config.rootdir)
datafile = os.path.join(request.config.rootdir, "fixture_chapter", "data.yaml")
print("\nyaml文件路径:%s" % datafile)
with open(datafile) as f:
data_config = yaml.load(f.read(), Loader=yaml.SafeLoader)
print("\n读取的yaml信息:%s" % data_config)
return data_config
再次执行
pytest -s test_fixture_request.py
request.getfixturevalue
request.getfixturevalue 获取 fixture 的返回值
import pytest
@pytest.fixture(scope="session")
def my_fixture1():
aaa = "AllTests软件测试"
return aaa
@pytest.fixture(scope="session")
def my_fixture2():
bbb = "1234567890"
return bbb
@pytest.fixture(scope="session", params=["my_fixture1", "my_fixture2"])
def get_fixture_value(request):
ccc = request.getfixturevalue(request.param)
return ccc
def test_case(get_fixture_value):
print("\n获取到的fixture值:" + get_fixture_value)
fixture之pytestconfig
获取配置对象,除了 request.config 方法,还有一种方法也是可以的,那就是pytestconfig。
pytestconfig 是一个内置 fixture,用于获取配置对象。实际调用 pytestconfig 方法,其实就是返回 request.config。
方法:
- pytestconfig.getoption() 获取命令行参数
- pytestconfig.getini() 获取 ini 配置文件参数
示例一:pytestconfig.getoption() 获取命令行参数
import pytest
@pytest.fixture
def cmd_param(pytestconfig):
return pytestconfig.getoption("--tb")
def test_getoption1(pytestconfig):
param = pytestconfig.getoption("--tb")
print("\n获取到命令行参数:%s" % param)
def test_getoption2(cmd_param):
print("\n获取到命令行参数:%s" % cmd_param)
执行如下命令
pytest -s test_fixture_pytestconfig.py --tb=long
命令行参数–tb=style,可以设置用例报错的时候回溯打印的内容。
style的值可以设置的打印模式:auto、long、short、line、native、no
示例二:pytestconfig.getini() 获取 ini 配置文件参数
项目的根目录创建pytest.ini文件,用于填写配置参数,并安装pytest-base-url包
文件内容:
[pytest]
base_url = https://www.cnblogs.com/alltests/
创建test_fixture_pytestconfig2.py文件
def test_getini(pytestconfig):
base_url = pytestconfig.getini("base_url")
print("\n获取到ini文件参数 :%s" % base_url)
执行如下命令
pytest -s test_fixture_pytestconfig2.py