前言
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的测试函数使用
以上为内容纯属个人理解,如有不足,欢迎各位大神指正,转载请注明出处!