pytest封神之路第三步 精通fixture

首先放一句“狠话”。

如果你不会fixture,那么你最好别说自己会pytest。

(只是为了烘托主题哈,手上的砖头可以放下了,手动滑稽)

fixture是什么

看看源码

def fixture(
    callable_or_scope=None,*args,scope="function",params=None,autouse=False,ids=None,name=None
):
    """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.

    :arg scope: the scope for which this fixture is shared,one of
                ``"function"`` (default),``"class"``,``"module"``,``"package"`` or ``"session"`` (``"package"`` is considered **experimental**
                at this time).

                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.

    :arg 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``.

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

    :arg 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.

    :arg 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>')``.
    """
    if params is not None:
        params = list(params)

    fixture_function,arguments = _parse_fixture_args(
        callable_or_scope,scope=scope,params=params,autouse=autouse,ids=ids,name=name,)
    scope = arguments.get("scope")
    params = arguments.get("params")
    autouse = arguments.get("autouse")
    ids = arguments.get("ids")
    name = arguments.get("name")

    if fixture_function and params is None and autouse is False:
        # direct decoration
        return FixtureFunctionMarker(scope,params,autouse,name=name)(
            fixture_function
        )

    return FixtureFunctionMarker(scope,name=name)

总结一下

【定义】

  • fixture是一个函数,在函数上添加注解@pytest.fixture来定义
  • 定义在conftest.py中,无需import就可以调用
  • 定义在其他文件中,import后也可以调用
  • 定义在相同文件中,直接调用

【使用】

  • 第一种使用方式是@pytest.mark.usefixtures(fixturename)(如果修饰TestClass能对类中所有方法生效)
  • 第二种使用方式是作为函数参数
  • 第三种使用方式是autouse(不需要显示调用,自动运行)

conftest.py

我们常常会把fixture定义到conftest.py文件中。

这是pytest固定的文件名,不能自定义。

必须放在package下,也就是目录中有__init__.py。

conftest.py中的fixture可以用在当前目录及其子目录,不需要import,pytest会自动找。

可以创建多个conftest.py文件,同名fixture查找时会优先用最近的。

依赖注入

fixture实现了依赖注入。依赖注入是控制反转(IoC, Inversion of Control)的一种技术形式。

简单理解一下什么是依赖注入和控制反转

实在是妙啊!我们可以在不修改当前函数代码逻辑的情况下,通过fixture来额外添加一些处理。

入门示例

# content of ./test_smtpsimple.py
import smtplib

import pytest


@pytest.fixture
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com",587,timeout=5)


def test_ehlo(smtp_connection):
    response,msg = smtp_connection.ehlo()
    assert response == 250
    assert 0  # for demo purposes

执行后程序处理逻辑

  1. pytest找到test_开头的函数,发现需要名字为smtp_connection的fixture,就去找
  2. 找到之后,调用smtp_connection(),return了SMTP的实例
  3. 调用test_ehlo(<smtp_connection instance>) ,入参smtp_connection等于fixture return的值

如果想看文件定义了哪些fixture,可以使用命令,_前缀的需要跟上-v

pytest --fixtures test_simplefactory.py

fixture scope & order

既然到处都可以定义fixture,那多了岂不就乱了?

pytest规定了fxture的运行范围和运行顺序。

fixture的范围通过参数scope来指定

@pytest.fixture(scope="module")

默认是function,可以选择function,class,module,package 或 session。

fixture都是在test第一次调用时创建,根据scope的不同有不同的运行和销毁方式

  • function 每个函数运行一次,函数结束时销毁
  • class 每个类运行一次,类结束时销毁
  • module 每个模块运行一次,模块结束时销毁
  • package 每个包运行一次,包结束时销毁
  • session 每个会话运行一次,会话结束时销毁

fixture的顺序优先按scope从大到小,session > package > module > class > function。

如果scope相同,就按test调用先后顺序,以及fixture之间的依赖关系。

autouse的fixture会优先于相同scope的其他fixture。

示例

import pytest

# fixtures documentation order example
order = []


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


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


@pytest.fixture
def f1(f3):
    order.append("f1")


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


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


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

def test_order(f1,m1,f2,s1):
    assert order == ["s1","m1","a1","f3","f1","f2"]

虽然test_order()是按f1,s1调用的,但是结果却不是按这个顺序

  1. s1 scope为session
  2. m1 scope为module
  3. a1 autouse,默认function,后于session、module,先于function其他fixture
  4. f3 被f1依赖
  5. f1 test_order()参数列表第1个
  6. f2 test_order()参数列表第3个

fixture嵌套

fixture装饰的是函数,那函数也有入参咯。

fixture装饰的函数入参,只能是其他fixture。

示例,f1依赖f3,如果不定义f3的话,执行会报错fixture 'f3' not found

@pytest.fixture
def f1(f3):
    order.append("f1")


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

def test_order(f1):
    pass

从test传值给fixture

借助request,可以把test中的值传递给fixture。

示例1,smtp_connection可以使用module中的smtpserver

# content of conftest.py
import smtplib

import pytest


@pytest.fixture(scope="module")
def smtp_connection(request):
    server = getattr(request.module,"smtpserver","smtp.gmail.com")
    smtp_connection = smtplib.SMTP(server,timeout=5)
    yield smtp_connection
    print("finalizing {} ({})".format(smtp_connection,server))
    smtp_connection.close()

# content of test_anothersmtp.py
smtpserver = "mail.python.org"  # will be read by smtp fixture


def test_showhelo(smtp_connection):
    assert 0,smtp_connection.helo()

示例2,结合request+mark,把fixt_data从test_fixt传值给了fixt

import pytest


@pytest.fixture
def fixt(request):
    marker = request.node.get_closest_marker("fixt_data")
    if marker is None:
        # Handle missing marker in some way...
        data = None
    else:
        data = marker.args[0]
    # Do something with the data
    return data


@pytest.mark.fixt_data(42)
def test_fixt(fixt):
    assert fixt == 42

fixture setup / teardown

其他测试框架unittest/testng,都定义了setup和teardown函数/方法,用来测试前初始化和测试后清理。

pytest也有,不过是兼容unittest等弄的,不推荐!

from loguru import logger


def setup():
    logger.info("setup")


def teardown():
    logger.info("teardown")


def test():
    pass

建议使用fixture。

setup,fixture可以定义autouse来实现初始化。

@pytest.fixture(autouse=True)

autouse的fixture不需要调用,会自己运行,和test放到相同scope,就能实现setup的效果。

autouse使用说明

  • autouse遵循scope的规则,scope="session"整个会话只会运行1次,其他同理
  • autouse定义在module中,module中的所有function都会用它(如果scope="module",只运行1次,如果scope="function",会运行多次)
  • autouse定义在conftest.py,conftest覆盖的test都会用它
  • autouse定义在plugin中,安装plugin的test都会用它
  • 在使用autouse时需要同时注意scope和定义位置

示例,transact默认scope是function,会在每个test函数执行前自动运行

# content of test_db_transact.py
import pytest


class DB:
    def __init__(self):
        self.intransaction = []

    def begin(self,name):
        self.intransaction.append(name)

    def rollback(self):
        self.intransaction.pop()


@pytest.fixture(scope="module")
def db():
    return DB()


class TestClass:
    @pytest.fixture(autouse=True)
    def transact(self,request,db):
        db.begin(request.function.__name__)
        yield
        db.rollback()

    def test_method1(self,db):
        assert db.intransaction == ["test_method1"]

    def test_method2(self,db):
        assert db.intransaction == ["test_method2"]

这个例子不用autouse,用conftest.py也能实现

# content of conftest.py
@pytest.fixture
def transact(request,db):
    db.begin()
    yield
    db.rollback()
@pytest.mark.usefixtures("transact")
class TestClass:
    def test_method1(self):
        ...

teardown,可以在fixture中使用yield关键字来实现清理。

示例,scope为module,在module结束时,会执行yield后面的print()和smtp_connection.close()

# content of conftest.py
import smtplib

import pytest


@pytest.fixture(scope="module")
def smtp_connection():
    smtp_connection = smtplib.SMTP("smtp.gmail.com",timeout=5)
    yield smtp_connection  # provide the fixture value
    print("teardown smtp")
    smtp_connection.close()

可以使用with关键字进一步简化,with会自动清理上下文,执行smtp_connection.close()

# content of test_yield2.py
import smtplib

import pytest


@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com",timeout=5) as smtp_connection:
        yield smtp_connection  # provide the fixture value

fixture参数化

后续会专门讲“pytest参数化”,这里就先跳过,请各位见谅啦。

因为我觉得想用pytest做参数化,一定是先到参数化的文章里面找,而不是到fixture。

把这部分放到参数化,更便于以后检索。

简要回顾

本文开头通过源码介绍了fixture是什么,并简单总结定义和用法。然后对依赖注入进行了解释,以更好理解fixture技术的原理。入门示例给出了官网的例子,以此展开讲了范围、顺序、嵌套、传值,以及初始化和清理的知识。

如果遇到问题,欢迎沟通讨论。

更多实践内容,请关注后续篇章《tep最佳实践》。

参考资料

https://en.wikipedia.org/wiki/Dependency_injection

https://en.wikipedia.org/wiki/Inversion_of_control

https://docs.pytest.org/en/stable/contents.html#toc

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


目录1、前言2、mark的使用(一)注册自定义标记(二)在测试用例上标记(三)执行3、扩展(一)在同一个测试用例上使用多个标记(二)在测试类上使用标记1、前言在自动化测试工作中我们有时候并不需要测试所有的测试用例,比如在冒烟测试阶段,我们只需要测试基本功能是否正常就可以了。在pytest中提供
用例执行状态用例执行完成后,每条用例都有自己的状态,常见的状态有passed:测试通过failed:断言失败error:用例本身写的质量不行,本身代码报错(譬如:fixture不存在,fixture里面有报错)xfail:预期失败,加了 @pytest.mark.xfail()  error的栗子一:参数不存在 defpwd():prin
什么是conftest.py可以理解成一个专门存放fixture的配置文件 实际开发场景多个测试用例文件(test_*.py)的所有用例都需要用登录功能来作为前置操作,那就不能把登录功能写到某个用例文件中去了 如何解决上述场景问题?conftest.py的出现,就是为了解决上述问题,单独管理一些全局的
前言pytest默认执行用例是根据项目下的文件名称按ascii码去收集运行的;文件中的用例是从上往下按顺序执行的。pytest_collection_modifyitems这个函数顾名思义就是收集测试用例、改变用例的执行顺序的。【严格意义上来说,我们在用例设计原则上用例就不要有依赖顺序,这样才能更好
当我们对测试用例进行参数化时,使用@pytest.mark.parametrize的ids参数自定义测试用例的标题,当标题中有中文时,控制台和测试报告中会出现Unicode编码问题,这看起来特别像乱码,我们想让中文正常展示出来,需要用到pytest框架的钩子函数pytest_collection_modifyitems。先看问题:#file_n
前言:什么是元数据?元数据是关于数据的描述,存储着关于数据的信息,为人们更方便地检索信息提供了帮助。pytest框架里面的元数据可以使用pytest-metadata插件实现。文档地址https://pypi.org/project/pytest-metadata/未安装插件pytest-metadata之前执行:环境搭建:使用
前言前面一篇讲了setup、teardown可以实现在执行用例前或结束后加入一些操作,但这种都是针对整个脚本全局生效的如果有以下场景:用例1需要先登录,用例2不需要登录,用例3需要先登录。很显然无法用setup和teardown来实现了fixture可以让我们自定义测试用例的前置条件 
前言:写完一个项目的自动化用例之后,发现有些用例运行较慢,影响整体的用例运行速度,于是领导说找出运行慢的那几个用例优化下。--durations参数可以统计出每个用例运行的时间,对用例的时间做个排序。pytest-h查看命令行参数,关于--durations=N参数的使用方式--durations=N
钩子函数之pytest_addoption介绍:①pytest_addoption钩子函数可以让用户注册一个自定义的命令行参数,以便于用户在测试开始前将数据从外部(如:控制台)传递给程序;【程序根据获取的用户传递的自定义的参数值来做一些事情】②pytest_addoption钩子函数一般和内置fixturepytestcon
[pytest]#命令行参数----空格分隔,可添加多个命令行参数-所有参数均为插件包的参数addopts=-s-reruns1--html=..eporteport.html#测试路径----当前目录下的scripts文件夹-可自定义testpaths=../scripts#搜索文件名----当前目录下的scripts文件夹下,以test_开头,以.py
python通用测试框架大多数人用的是unittest+HTMLTestRunner,这段时间看到了pytest文档,发现这个框架和丰富的plugins很好用,所以来学习下pytest. image.pngpytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:简单灵活,容易上手支持参数化能够支持简单的单
1、装饰器,放在函数前面,跳过用例 @pytest.mark.skip(reason="nowayofcurrentlytestingthis")importpytestdeftest1():print('操作1')print("-----------------------------------------------")@pytest.mark.skip(reason="nowayofcur
本文实例为大家分享了python下载微信公众号相关文章的具体代码,供大家参考,具体内容如下目的:从零开始学自动化测试公众号中下载“pytest"一系列文档1、搜索微信号文章关键字搜索2、对搜索结果前N页进行解析,获取文章标题和对应URL主要使用的是requests和bs4中的Beautifulsoup
From:https://www.jianshu.com/p/54b0f4016300一.fixture介绍fixture是pytest的一个闪光点,pytest要精通怎么能不学习fixture呢?跟着我一起深入学习fixture吧。其实unittest和nose都支持fixture,但是pytest做得更炫。fixture是pytest特有的功能,它用pytest.fixture标识,定义在函
参数化有两种方式:1、@pytest.mark.parametrize2、利用conftest.py里的pytest_generate_tests 1中的例子如下:@pytest.mark.parametrize("test_input,expected",[("3+5",8),("2+4",6),("6*9",42)])deftest_eval(test_input,expected):
pytest优于其他测试框架的地方:1、简单的测试可以简单的写2、复杂的测试也可以简单的写3、测试的可读性强4、易于上手5、断言失败仅使用原生assert关键字,而不是self.assertEqual()或者self.assertLessThan()6、pytest可以运行有unitest和nose编写的测试用例pytest不依赖pyth
学习python的pytest框架需要的基础知识和学习准备测试从业者学习python应该掌握的内容:首先是变量和数据类型,其次列表、字典以及Json的一些处理,再者就是循环判断以及函数或类这些内容。其中的重点:1.循环判断以及字典这块是重点2.函数和类,类的学习这块要花较多时间去学
前言pytest可以支持自定义标记,自定义标记可以把一个web项目划分多个模块,然后指定模块名称执行。app自动化的时候,如果想android和ios公用一套代码时,也可以使用标记功能,标明哪些是ios用例,哪些是android的,运行代码时候指定mark名称运行就可以mark标记1.以下用例,标记test_send_http(
unittest参考文档: https://docs.python.org/3/library/unittest.htmlunittest笔记TheunittestunittestingframeworkwasoriginallyinspiredbyJUnitandhasasimilarflavorasmajorunittestingframeworksinotherlanguages.Itsupportstestautomation,shar
fixture场景一:参数传入代码如下:运行结果: