pytest零基础入门到精通(03)fixture的运用

Posted 七月的小尾巴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pytest零基础入门到精通(03)fixture的运用相关的知识,希望对你有一定的参考价值。

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

以上是关于pytest零基础入门到精通(03)fixture的运用的主要内容,如果未能解决你的问题,请参考以下文章

pytest接口自动化零基础入门到精通(01)入门基础篇

pytest接口自动化零基础入门到精通(02)pytest前后置

pytest零基础入门到精通(05)Allure报告的隐藏用法

pytest零基础入门到精通(05)Moke技术详解

华为出品Python入门教程:从零基础入门到精通,这一篇就够了

Linux零基础快速入门到精通 导学