pytest测试框架系列 - Pytest Fixture和conftest.py还能这样使用?

前言

Fixture是pytest的非常核心功能之一,在不改变被装饰函数的前提下对函数进行功能增强,经常用于测试用例前置和后置工作。与setup/teardown类似,但更强大灵活。

fixture的优势

  • fixture命名方式灵活,不局限于 setup 和teardown 那几个命名规则
  • conftest.py 配置里可以实现数据共享,能够自动搜索需要的fixture
  • fixture 配置不同的参数可以轻松实现跨文件、session会话共享

fixture工作原理

  • 在普通函数上使用@Pytest.fixture()装饰器,声明函数为一个fixture函数
  • 如果测试用例函数的参数列表中存在fixture的函数名或者使用@pytest.mark.usefixtures(fixture_name)
  • 在测试执行阶段,pytest执行用例之前执行fixture函数功能
  • 如果fixture装饰的函数无返回值,相当于用例前置操作,否则相当于传参
  • 如果fixture设置了后置操作,则用例执行完成后进行执行

fixture搜索顺序

  • 第一步:优先搜索测试所在的模块
  • 第二步:搜索模块同一文件路径下的conftest.py
  • 第三步:找不到再搜索上一层的conftest.py,直到项目根目录

fixture 函数说明

def fixture(
    fixture_function: Optional[_FixtureFunction] = None,
    *,
    scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function",
    params: Optional[Iterable[object]] = None,
    autouse: bool = False,
    ids: Optional[
        Union[
            Iterable[Union[None, str, float, int, bool]],
            Callable[[Any], Optional[object]],
        ]
    ] = None,
    name: Optional[str] = None,
) -> Union[FixtureFunctionMarker, _FixtureFunction]:
    """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.

    :param scope:
        The scope for which this fixture is shared; one of ``"function"``
        (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``.

        This parameter may also be a callable which receives ``(fixture_name, config)``
        as parameters, and must return a ``str`` with one of the values mentioned above.

        See :ref:`dynamic scope` in the docs for more information.

    :param params:
        An optional list of parameters which will cause multiple invocations
        of the fixture function and all of the tests using it. The current
        parameter is available in ``request.param``.

    :param autouse:
        If True, the fixture func is activated for all tests that can see it.
        If False (the default), an explicit reference is needed to activate
        the fixture.

    :param ids:
        List of string ids each corresponding to the params so that they are
        part of the test id. If no ids are provided they will be generated
        automatically from the params.

    :param name:
        The name of the fixture. This defaults to the name of the decorated
        function. If a fixture is used in the same module in which it is
        defined, the function name of the fixture will be shadowed by the
        function arg that requests the fixture; one way to resolve this is to
        name the decorated function ``fixture_<fixturename>`` and then use
        ``@pytest.fixture(name='<fixturename>')``.
    """
    """
    翻译:
     可以使用此装饰器(带或不带参数)来定义fixture功能。 
     
     fixture功能的名称可以在后面使用引用它会在运行测试之前调用它:test模块或类可以使用
     pytest.mark.usefixtures(fixturename标记)。 
     
     测试功能可以直接使用fixture名称作为输入参数,在这种情况下,夹具实例从fixture返回功能将被注入。
     
     fixture可以使用``return`` or``yield`` 语句返回测试函数所需要的参数值,在``yield``后面的代码块
     不管测试的执行结果怎么样都会执行
     
    :arg scope: scope 有四个级别参数 "function" (默认), "class", "module" or "session".

    :arg params: 一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它

    :arg autouse:  如果为True,自动执行fixture函数,所有函数都可以使用。 如果为False(默认值)
    则手动使用fixture才能调用

    :arg ids: 每个字符串id的列表,每个字符串对应于params 这样他们就是测试ID的一部分。 
    如果没有提供ID它们将从params自动生成

    :arg name:   fixture的名称。 默认为装饰函数的名称。 如果设置了name的值,在使用fixture时需要
    使用设置的name,否则无法识别
    """

fixture参数详解

使用语法:

@pytest.fixture(scope = "function",params=None,autouse=False,ids=None,name=None)
def login():
    print("我是login函数")

scope 参数

  • 说明:可以认为是fixture的作用域,默认:function,还有class、module、package、session四个
  • 区别:
取值 范围 说明
function 函数级 每一个函数或方法都会调用
class 类级 每个测试类只运行一次
module 模块级 每一个.py文件调用一次
session 会话级 每次会话只需要运行一次,一般用于打开浏览器、启动APP、登录等操作

关注点

  • fixture可以给函数和类使用
  • fixture通过测试用例参数方式使用,这样fixture的返回值可以通过fixture名称作为参数传递给测试用例,也可以使用@pytest.mark.usefixtures("login")方式,但无法给测试用例传递fixture的返回值
  • fixture函数内可以调用其他fixture函数,必须要通过函数参数方式
  • 非测试用例的函数无法使用fixture
    作用范围示例:

scope = "function"

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 14:39
# @Author  : king
# @File    :test_fixture.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(scope="function")
def login():
    print("我是login函数。。")
    return "king"

# fixture内调用其他fixture
@pytest.fixture(scope="function")
def login_01(login):
    print("我是调用了 login的fixture函数")

# 通过参数方式使用fixture
def test_01(login):
    print("我是 test_01 测试用例\n")

# 通过@pytest.mark.usefixtures("login")调用fixture
@pytest.mark.usefixtures("login")
def test_02():
    print("我是 test_02 测试用例\n")

# 通过参数方式使用调用其他fixture的fixture
def test_03(login_01):
    print("我是 test_03 测试用例\n")

# 给类使用fixture
@pytest.mark.usefixtures("login")
class TestFixture:
    def test_one(self):
        print("我是类里面的 test_one 测试用例")

    def test_two(self):
        print("我是类里面的 test_two 测试用例")

# 非test开头函数调用fixture
def reg_01(login):
    print("我是reg函数\n")

# 非test开头函数使用@pytest.mark.usefixtures("login")调用fixture
@pytest.mark.usefixtures("login")
def reg_02():
    print("我是reg函数\n")

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


执行结果为:


在这里插入图片描述

scope = "class"

示例:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 15:35
# @Author  : king
# @File    :test_fixture_class.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(scope="class")
def login():
    print("我是login函数。。")
    return "king"

# 通过参数方式使用fixture
def test_01(login):
    print("我是 test_01 测试用例\n")

# 通过参数方式使用fixture
def test_02(login):
    print("我是 test_02 测试用例\n")

@pytest.mark.usefixtures("login")
class TestFixture:
    def test_one(self):
        print("我是类里面的 test_one 测试用例")

    def test_two(self):
        print("我是类里面的 test_two 测试用例")

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

执行结果:


在这里插入图片描述

scope = "module"

示例:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 16:52
# @Author  : king
# @File    :test_fixture_module.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(scope="module")
def login():
    print("我是login函数。。")
    return "king"

# 通过参数方式使用fixture
def test_01(login):
    print("我是 test_01 测试用例\n")

# 通过参数方式使用fixture
def test_02(login):
    print("我是 test_02 测试用例\n")

@pytest.mark.usefixtures("login")
class TestFixture:
    def test_one(self):
        print("我是类里面的 test_one 测试用例")

    def test_two(self):
        print("我是类里面的 test_two 测试用例")

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

执行结果为:


在这里插入图片描述

scope = "session"

  • 注意:session代表会话级,就是从启动测试到结束测试,看作为一次session会话,scope = "session"使用到后面conftest.py详细讲解

params 参数

  • fixture的可选形参列表,支持列表传入
  • params 参数包含几个,调用时就会执行几次
  • 可与参数ids一起使用,作为每个参数的标识,详见ids
  • 需要使用时,参数调用写法固定为:Request.param
    示例:
# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:13
# @Author  : king
# @File    :test_fixture_params.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(params=[1, 2, 3, 4, 5])
def login(request):
    print("我是login函数。。")
    return request.param

# 通过参数方式使用fixture
def test_01(login):
    print("我是 test_01 测试用例-params- {}\n".format(login))

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

执行结果:


在这里插入图片描述

autouse 参数

  • 默认False
  • 如果设置为True,则每个测试函数都会自动调用该fixture,无需传入fixture函数名,作用范围跟着scope走(注意使用)
    示例:
# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:25
# @Author  : king
# @File    :test_fixture_autouse.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(autouse=True)
def login():
    print("我是login函数。。")

# 通过参数方式使用fixture
def test_01():
    print("我是 test_01 测试用例\n")

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

执行结果:


在这里插入图片描述

ids 参数

  • 用例标题,需要与params配合使用,一对一关系

未配置ids时:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:29
# @Author  : king
# @File    :test_fixture_ids.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(params=[1, 2, 3])
def login(request):
    return request.param

# 通过参数方式使用fixture
def test_01(login):
    print("我是 test_01 测试用例\n")

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

执行结果:
[图片上传失败...(image-7d1ae1-1625479261110)]

配置了ids的示例:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:29
# @Author  : king
# @File    :test_fixture_ids.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(params=[1, 2, 3], ids=["case01", "case02", "case03"])
def login(request):
    return request.param

# 通过参数方式使用fixture
def test_01(login):
    print("我是 test_01 测试用例\n")

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

执行结果:


在这里插入图片描述

问题:

当我们多个测试用例文件(test_*.py)的所有用例都需要用登录、打开浏览器、启动APP等功能来作为前置操作,那就不能把登录、打开浏览器、启动APP功能写到某个用例文件,如何解决呢?

解决方案:引入conftest.py文件,为了解决上述问题,单独管理一些全局的fixture

conftest.py使用

说明:

  • pytest会默认读取conftest.py里面的所有fixture
  • conftest.py 文件名称是固定的,不能随意改动
  • conftest.py只对同一个package下的所有测试用例生效,并且有init.py文件
  • 不同目录可以有自己的conftest.py,一个项目中可以有多个conftest.py
  • pytest会自动查找项目中的conftest.py文件,逐层往上查找

示例:

  • 项目目录
case
│  conftest.py
│  __init__.py
│
├─case01
│      conftest.py
│      test_01.py
│      __init__.py
│
└─case02
        conftest.py
        test_02.py
        __init__.py

case/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:04
# @Author  : king
# @File    :conftest.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(scope="session")
def login():
    print("我是case目录的login")

@pytest.fixture(scope="session")
def open_browser():
    print("我是case目录的 open_browser")

case01/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:06
# @Author  : king
# @File    :conftest.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(scope="session")
def login():
    print("我是case01目录的login")

case01/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:04
# @Author  : king
# @File    :test_01.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】

def test_01(login):
    print("我是case01 里面test_01 测试用例")

def test_02(open_browser):
    print("我是case01 里面test_02 测试用例")

case02/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:07
# @Author  : king
# @File    :conftest.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】
import pytest

@pytest.fixture(scope="session")
def _login():
    print("我是case02 目录的login")

case02/test_02.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:04
# @Author  : king
# @File    :test_02.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【测试之路笔记】

def test_02(login):
    print("我是case02的 test_02 测试用例")

在case目录下,执行 pytest -s

在这里插入图片描述

注意点

  • 外层的fixture不能调用内层的fixture
  • 不同包下面的fixture只能当前包使用,不能被其他包使用,例如case01下面的fixture不能被case02的测试函数使用

以上为内容纯属个人理解,如有不足,欢迎各位大神指正,转载请注明出处!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容