Pytest中fixture的scope详解

pytest作为Python技术栈下最主流的测试框架,功能极为强大和灵活。其中Fixture夹具是它的核心。而且pytest中对Fixture的作用范围也做了不同区分,能为我们利用fixture带来很好地灵活性。

下面我们就来了解下这里不同scope的作用

fixture的scope定义

首先根据官网的说明,Pytest中fixture的作用范围支持5种设定,分别是function(默认值), classs, module, package, session

作用范围 说明
function 默认值,对每个测试方法(函数)生效,生命周期在测试方法级别
class 对测试类生效,生命周期在测试类级别
module 对测试模块生效,生命周期在模块(文件)级别
package 对测试包生效,生命周期在测试包(目录)级别
session 对测试会话生效,生命周期在会话(一次pytest运行)级别

下面结合代码来说明,假设目前有这样的代码结构

run_params是被测方法

python 复制代码
def deal_params(p):  
    print(f"input :{p}")  
    if type(p) is int:  
        return p*10  
    if type(p) is str:  
        return p*3  
    if type(p) in (tuple, list):  
        return "_".join(p)  
    else:  
        raise TypeError

test_ch_param, test_fixture_scope中分别定义了参数化和在测试类中的不同测试方法

python 复制代码
import pytest

@pytest.mark.parametrize("param",[10, "城下秋草", "软件测试", ("示例", "代码")])  
def test_params_mark(param):  
    print(deal_params(param))
python 复制代码
import pytest  
 
class TestFixtureScope1:  
    def test_int(self):  
        assert deal_params(2) == 20  
  
    def test_str(self):  
        assert deal_params("秋草") == "秋草秋草秋草"  
  
class TestFixtureScope2:  
    def test_list(self):  
        assert deal_params(["城下","秋草"]) == "城下_秋草"  
  
    def test_dict(self):  
        with pytest.raises(TypeError):  
            deal_params({"name": "秋草"})

在公共方法文件conftest.py中定义fixture: prepare, 设置了autouse=True,即会根据fixture的设置范围自动应用

python 复制代码
@pytest.fixture(autouse=True, scope='function')  
def prepare():  
    print('-----some setup actions.....')  
    yield  
    print('-----some teardown actions!!')

这里我们分别调整prepare的scope为不同取值,然后得到对应的输出

function

console 复制代码
(.venv) C:\Chengxiaqiucao
pytest
======================================== test session starts =========================================
collected 8 items                                                                                     

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[10] -----some setup actions.....
input :10
100
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[城下秋草] -----some setup actions.....
input :城下秋草
城下秋草城下秋草城下秋草
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[软件测试] -----some setup actions.....
input :软件测试
软件测试软件测试软件测试
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[param3] -----some setup actions.....
input :('示例', '代码')
示例_代码
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_int -----some setup actions.....
input :2
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_str -----some setup actions.....      
input :秋草
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_list -----some setup actions.....     
input :['城下', '秋草']
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_dict -----some setup actions.....     
input :{'name': '秋草'}
PASSED-----some teardown actions!!


========================================= 8 passed in 0.27s ========================================== 

fixture运行了8次

class

console 复制代码
(.venv) C:\Chengxiaqiucao
pytest
======================================== test session starts =========================================
collected 8 items                                                                                     

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[10] -----some setup actions.....
input :10
100
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[城下秋草] -----some setup actions.....
input :城下秋草
城下秋草城下秋草城下秋草
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[软件测试] -----some setup actions.....
input :软件测试
软件测试软件测试软件测试
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[param3] -----some setup actions.....
input :('示例', '代码')
示例_代码
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_int -----some setup actions.....
input :2
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_str input :秋草
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_list -----some setup actions.....     
input :['城下', '秋草']
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_dict input :{'name': '秋草'}
PASSED-----some teardown actions!!


========================================= 8 passed in 0.27s ========================================== 

test_ch_param中的测试方法,因为直接定义在文件中,也属于类级别,所以每次赋值参数,fixture也被调用。 而 test_fixture_scope中明确定义了两个测试类,所以运行了2次

module

console 复制代码
(.venv) C:\Chengxiaqiucao
pytest
======================================== test session starts =========================================
collected 8 items                                                                                     

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[10] -----some setup actions.....
input :10
100
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[城下秋草] input :城下秋草
城下秋草城下秋草城下秋草
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[软件测试] input :软件测试
软件测试软件测试软件测试
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[param3] input :('示例', '代码')
示例_代码
PASSED-----some teardown actions!!

BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_int -----some setup actions.....
input :2
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_str input :秋草
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_list input :['城下', '秋草']
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_dict input :{'name': '秋草'}
PASSED-----some teardown actions!!


========================================= 8 passed in 0.27s ========================================== 

修改为module范围后,可以看到,每个模块文件调用了一次fixture

package

console 复制代码
(.venv) C:\Chengxiaqiucao
pytest
======================================== test session starts =========================================
collected 8 items                                                                                     

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[10] -----some setup actions.....
input :10
100
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[城下秋草] input :城下秋草
城下秋草城下秋草城下秋草
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[软件测试] input :软件测试
软件测试软件测试软件测试
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[param3] input :('示例', '代码')
示例_代码
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_int input :2
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_str input :秋草
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_list input :['城下', '秋草']
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_dict input :{'name': '秋草'}
PASSED-----some teardown actions!!


========================================= 8 passed in 0.27s ========================================== 

修改为package, 这是因为两个测试文件位于同一个package内, 所以运行了一次

session

console 复制代码
(.venv) C:\Chengxiaqiucao
pytest
======================================== test session starts =========================================
collected 8 items                                                                                     

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[10] -----some setup actions.....
input :10
100
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[城下秋草] input :城下秋草
城下秋草城下秋草城下秋草
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[软件测试] input :软件测试
软件测试软件测试软件测试
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[param3] input :('示例', '代码')
示例_代码
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_int input :2
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_str input :秋草
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_list input :['城下', '秋草']
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_dict input :{'name': '秋草'}
PASSED-----some teardown actions!!


========================================= 8 passed in 0.27s ========================================== 

最后,当设置为session时,也就是运行pytest的一次执行会话,才会触发一次fixture调用

所以可以看到,我们通过fixture的不同scope定义,可以根据需要,来确定我们编写的fixture夹具的作用范围。有很好的灵活性

复杂fixture的scope灵活定义

有时在实际使用的时候,特别是我们的一些fixture初始化工作比较复杂但同时在不同作用范围下都可能会用到,这时如果仅仅因为针对不同的作用范围,就要编写多个不同的fixture,代码就显得比较冗余。这时可以怎么处理呢? 其实可以利用上下文contextmanager来灵活实现

比如我们再编写一个fixture的基本代码上下文:

python 复制代码
@contextmanager  
def fixture_base():  
    print('~~~~~base fixture setup actions.....')  
    yield  
    print('~~~~~base fixture teardown actions!!')

然后针对不同的fixture,我们就可以根据不同的scope来定义不同的fixture并调用这里的context实现。 比如我们再定义一个scope为package的fixture

python 复制代码
@pytest.fixture(autouse=False, scope='package')  
def fixture_module():  
    """  
    对于复杂的fixture但希望灵活处理scope,可以将公共代码放到一个contextmanager中,  
    再针对不同scope定义相关对应fixture  
    """    with fixture_base() as result:  
        yield result

👍👍这个方法来自pytest的社区总结,原始问题链接

不同scope的执行顺序

上面例子中我们其实看到package和session的执行效果,因为测试方法都在同一个package中,所以效果上没什么差异。但其实不同scope也是有执行顺序的

顺序总结如下:

session > package > module > class > function

这里增加到两个fixture以后,执行的结果:

console 复制代码
(.venv) C:\Chengxiaqiucao
pytest
======================================== test session starts =========================================
collected 8 items                                                                                     

BlogDemo/testDemo/test_ch_parm.py::test_params_mark[10] -----some setup actions.....
~~~~~base fixture setup actions.....
input :10
100
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[城下秋草] input :城下秋草
城下秋草城下秋草城下秋草
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[软件测试] input :软件测试
软件测试软件测试软件测试
PASSED
BlogDemo/testDemo/test_ch_parm.py::test_params_mark[param3] input :('示例', '代码')
示例_代码
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_int input :2
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope1::test_str input :秋草
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_list input :['城下', '秋草']
PASSED
BlogDemo/testDemo/test_fixture_scope.py::TestFixtureScope2::test_dict input :{'name': '秋草'}
PASSED~~~~~base fixture teardown actions!!
-----some teardown actions!!


========================================= 8 passed in 0.27s ========================================== 

可以看到 sessionpackage 更早执行,同时更晚被销毁。

那么以上就是关于pytest scope作用范围的总结


相关推荐
带土11 分钟前
6. C++智能指针(1)
开发语言·c++
linuxxx1102 分钟前
python变量引用的小案例
python
海南java第二人9 分钟前
SpringBoot启动流程深度解析:从入口到容器就绪的完整机制
java·开发语言
星火开发设计13 分钟前
C++ queue 全面解析与实战指南
java·开发语言·数据结构·c++·学习·知识·队列
2501_9361460420 分钟前
烟草叶片病害检测_YOLO11-C3k2-MSBlock模型详解
python
DICOM医学影像21 分钟前
2. go语言从零实现以太坊客户端-查询区块链账户余额
开发语言·golang·区块链·以太坊·web3.0·hardhat
Data_agent32 分钟前
Python 编程实战:函数与模块化编程及内置模块探索
开发语言·python
new_zhou32 分钟前
vs2019+qt工程中生成dump文件及调试
开发语言·qt·visual studio·dump调试
十铭忘34 分钟前
windows系统python开源项目环境配置1
人工智能·python
Generalzy1 小时前
langchain deepagent框架
人工智能·python·langchain