首先放一句“狠话”。

如果你不会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,
*args,
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, params, autouse, ids=ids, 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, m1, f2, 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, 587, 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", 587, 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", 587, 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

版权申明:本文为博主原创文章,转载请保留原文链接及作者。

如果您喜欢我写的文章,请关注公众号支持一下,谢谢哈哈哈。

pytest封神之路第三步 精通fixture的更多相关文章

  1. pytest封神之路第五步 参数化进阶

    用过unittest的朋友,肯定知道可以借助DDT实现参数化.用过JMeter的朋友,肯定知道JMeter自带了4种参数化方式(见参考资料).pytest同样支持参数化,而且很简单很实用. 语法 在& ...

  2. pytest封神之路第零步 快速入门

    背景:本文是在系列第五篇发表后的补充篇章,第一篇介绍了tep,可能对不熟悉pytest的朋友不够友好,特意补充入门篇,帮大家快速了解如何动手写pytest.如果你是从这篇文章第一次阅读,那么请忽略以上 ...

  3. pytest封神之路第四步 内置和自定义marker

    可以通过命令行查看所有marker,包括内置和自定义的 pytest --markers 内置marker 内置marker本文先讲usefixtures .filterwarnings .skip ...

  4. pytest封神之路第六步 断言技巧

    pytest的断言把Python语言简洁的优点发挥的淋漓尽致,因为它用的就是Python的标准断言assert. assert基础 assert用法 assert_stmt ::= "ass ...

  5. pytest封神之路第一步 tep介绍

    『 tep is a testing tool to help you write pytest more easily. Try Easy Pytest! 』 tep前身 tep的前身是接口自动化测 ...

  6. pytest封神之路第二步 132个命令行参数用法

    在Shell执行pytest -h可以看到pytest的命令行参数有这10大类,共132个 序号 类别 中文名 包含命令行参数数量 1 positional arguments 形参 1 2 gene ...

  7. 《带你装B,带你飞》pytest成神之路2- 执行用例规则和pycharm运行的三种姿态

    1. 简介 今天北京下的雪好大好美啊!!!哎呀,忘记拍照片了,自己想象一下吧.言归真传,今天还是开始pytest的学习和修炼,上一篇写完后群里反响各式各样的,几家欢乐几家愁,有的高兴说自己刚好要用到了 ...

  8. ExtJS学习之路第三步:理解引擎之下,ExtJS4中的类

    写写就发现,有些代码不查查源头,不明白是怎么回事?搜到这篇文章觉得还是收益匪浅,更容易读懂代码. Classes in Ext JS 4: Under the hood Countdown to Ex ...

  9. 阿里P7/P8学习路线图——技术封神之路

    一.基础篇 JVM JVM内存结构 堆.栈.方法区.直接内存.堆和栈区别 Java内存模型 内存可见性.重排序.顺序一致性.volatile.锁.final 垃圾回收 内存分配策略.垃圾收集器(G1) ...

随机推荐

  1. Spring中眼花缭乱的BeanDefinition

    本篇博客主要参考:Spring官网阅读(四)BeanDefinition(上) 引入主题 为什么要读Spring源码,有的人为了学习Spring中的先进思想,也有的人是为了更好的理解设计模式,当然也有 ...

  2. 【翻译】Promises/A+规范

    目录 介绍 译文 1. 术语(Terminology) 2. 要求(Requirements) 2.1 Promise状态 2.2 then方法 2.3 Promise解析程序 3. 注释 3.1 p ...

  3. jQuery 筛选方法

    前言 在jQuery中所有的东西全部都包含在jQuery对象中,并没有单独的DOM元素这一说法. 要想获取单独的DOM元素请用[index]获取,下面介绍的所有方法都会返回新的jQuery对象,而不是 ...

  4. HTTP基础--请求

    请求,由客户端向服务器端发出,可以分为4部分:请求方法(Request Method),请求的网址(Request URL),请求头(Request Headers),请求体(Request Body ...

  5. Java 实例 - 获取本机ip地址及主机名

    package guyu.day0824; import java.net.InetAddress; /** * @Author: Fred * @Date: 2020/8/24 09:39 */ p ...

  6. ent orm笔记1---快速尝鲜

    前几天看到消息Facebook孵化的ORM ent转为正式项目,出去好奇,简单体验了一下,使用上自己感觉比GORM好用,于是打算把官方的文档进行整理,也算是学习一下如何使用. 安装 ent orm 需 ...

  7. 23种设计模式 - 状态变化(Memento备忘录 - State)

    其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 状态变化 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的 ...

  8. boot磁盘空间大于80警报

    WARNING=80SPACE_USED=`df |grep '^/dev/sda' |tr -s ' ' %|cut -d% -f5|sort -n|tail -n1`[ "$SPACE_ ...

  9. Queries for Number of Palindromes(区间dp)

    You've got a string s = s1s2... s|s| of length |s|, consisting of lowercase English letters. There a ...

  10. Oracle中真正稳妥的求三甲的方法

    坐地铁回家路上忽然想起,三甲排名可能为多个,只取三名岂不荒谬.不信请看下面数据: create table tb_score( id number(4,0) primary key, name nva ...