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 ========================================== 

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

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


相关推荐
默_笙4 天前
🍙 给每个请求过安检:FastAPI 是怎么把校验写进类型注解的
python
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
qq_426003964 天前
启动playwright录制codegen生成自动化测试脚本
python·自动化
虎头金猫4 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
长沙三为智能科技4 天前
家政小程序开发从0到上线:五阶段交付流程与验收清单
python
伞伞悦读4 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
只睡四小时4 天前
Canvas 弹道联机实战:700 行 + 固定时间步长
python·websocket·html5·游戏开发·canvas
C语言小火车4 天前
C/C++ 为什么需要编译器?
开发语言·c++
奇思妙想聪明勤奋的小羊4 天前
DeepAgents第5章:子Agent 与上下文隔离—让 Agent学会委派
人工智能·python·学习·语言模型
lpfasd1234 天前
2026年第38周GitHub趋势周报
python·科技·github