Bootstrap

pytest学习记录(八)Fixture

简介

  • @pytest.fixture()修饰器用于声明函数是一个fixture

  • fixture 在其他函数,模块,类或整个工程调用它时会被激活并优先执行

  • fixture 通常会被用于完成测试前后进行预备、清理工作和重复操作(例如:定义测试数据集 、配置系统初始状态 、提供测试数据 、测试前后预备清理工作)

  • 用法用法 @pytest.fixture(scope=xxx,autouse=True,name=‘newname’,params=参数名,ids=idname)

通过conftest.py共享fixture

  • conftest.py

    • 是python模块,但是不可被测试文件导入,import conftest 的用法是不允许出现的。

    • 被pytest视作一个本地插件库。可以看做是供conftest.py所在目录及其子目录下所有测试使用的仓库。

  • 共享fixture

    • 多文件共享fixture
      如果你希望多个测试文件共享fixture,可以在某个公共目录下新建一个conftest.py,将fixture放在其中。

    • 某文件独享fixture
      如果你希望某个测试文件独享fixture,那么将fixture放在这个文件里面。或者将fixture放在一个和需要独享fixture测试文件同级的conftest.py中

    • 示例:
      test_cases_pytest.conftest.py:

    import pytest
    @pytest.fixture()
    def fuc_conftest():
        print(
            '我定义在test_cases_pytest.conftest.py的fixture,作用域未function,可以供test_cases_pytest路径下及其子路径下所有函数调用'
        )
    
    

    test_cases_pytest.test_fixtures.py:

    import pytest
    def id_func(fixture_value):
        """A function for generating ids."""
        t = fixture_value
        return 'id_' + str(t)
        
    @pytest.fixture(params=[1, 2, 3],ids=id_fuc)
    def need_data(request):  # 传入参数request 系统封装参数
        return request.param  # 取列表中单个值,默认的取值方式
    
    def test_a1(need_data):
        print("------->test_a")
    

    运行pytest -s -v .\test_fixtures.py::test_a1输出结果:
    在这里插入图片描述

使用fixture实现steup 和teardown

举例:
测试开始前需要配置并连接数据库(已使用test_start_demo_db()进行实现)
测试完成后需要清理数据并断开数据库连接(已使用test_stop_demo_db()进行实现)
示例:

@pytest.fixture()
def demo_db(tmpdir):
    # stetup: 连接数据库
    demo.test_start_demo_db()
    print("已连接数据库")
    yield  # 运行测试函数  
    # teardom:清理数据并断开数据库连接
    demo.test_stop_demo_db()
    print("数据库连接关闭")
def test_a(demo_db):     # ️ test_a方法中以变量的形式传入了被fixture标识的函数名        
    print("连接成功数据库后的一系列测试动作")        

运行结果:
在这里插入图片描述
若setup部分的代码,出现错误或断言失败,yield的代码和其后的代码将不会再执行,导致无法测试环境没有进行清理。可使用request的终结函数(request.addfinalizer())实现测试后的清理
可以使用**–setup-show** 参数查看fixture的回溯信息在这里插入图片描述

使用fixture传递测试数据

@pytest.fixture()
def some_list():
    yield [1, 'ss', None, {'bb': 33}]

def test_some_list(some_list):
    assert some_list == [1, 'ss', None, {'bb': 33}]
    assert some_list[3]['bb'] == 33

运行结果:
在这里插入图片描述

使用多个fixture

例如:在测试开始前在数据库中构造测试数据
fixtrue:
数据库连接和断开连接:demo_db()
要添加的数据集合:demo_3_datas()
添加数据: demo_db_add_3_datas(demo_db,demo_3_datas)
测试调用:test_add_increases_count(demo_db_add_3_datas)
在测试test_add_increases_count前,会先连接数据库并将数据集合中的3条数据添加到数据库
使用示例代码如下:

@pytest.fixture()
def demo_db(tmpdir):
    demo.test_start_demo_db()
    yield  # 运行测试函数
    demo.test_stop_demo_db()
    
@pytest.fixture()
def demo_3_datas():
    print('返回包含3条数据的list')
    
@pytest.fixture()
def demo_db_add_3_datas(demo_db, demo_3_datas):
    for data in demo_3_datas:
        print('逐条添加demo_3_datas中的数据')
        demo.add(data)
    
def test_add_increases_count(demo_db_add_3_datas):
    # 可以使用数据库中的数据,比如断言条数是否为3
    assert demo.count() == 3

以上只是使用思路的示例,若正常实现的话运行结果会是pass。

使用usefixtures指定fixture

用法:@pytest.mark.usefixtures(‘fixture1’,‘fixture2’)

@pytest.fixture()
def some_list():
    yield [1, 'ss', None, {'bb': 33}]
@pytest.mark.usefixtures('some_list')
def test_some_list2():
    assert some_list == [1, 'ss', None, {'bb': 33}]
    assert some_list[3]['bb'] == 33

这与在测试函数中添加fixture参数大体上差不多,但是使用usefixtures不能使用fixture的返回值,比如上代码运行后会failed:
在这里插入图片描述

使用scope指定fixture的作用范围

scope 是fixture的一个可选参数,用来控制fixture执行配置和销毁逻辑的频率。

  • scope =function(默认值):每个测试函数只运行一次
  • scope =class: 每个测试类只运行一次,类里面的测试方法或fixture共享此fixture
  • scope =module: 每个模块只运行一次,模块中的测试函数或者其他fixture均可共享此fixture
  • scope =session: 每次会话只运行一次,一次pytest会话中的所有测试函数,方法都共享此fixture
"""Demo fixture scope."""

import pytest


@pytest.fixture(scope='function')
def func_scope():
    """A function scope fixture."""


@pytest.fixture(scope='module')
def mod_scope():
    """A module scope fixture."""


@pytest.fixture(scope='session')
def sess_scope():
    """A session scope fixture."""


@pytest.fixture(scope='class')
def class_scope():
    """A class scope fixture."""


def test_1(sess_scope, class_scope, mod_scope, func_scope):
    """Test using session, module, and function scope fixtures."""


def test_2(sess_scope, class_scope, mod_scope, func_scope):
    """Demo is more fun with multiple tests."""


@pytest.mark.usefixtures('class_scope', 'mod_scope', 'sess_scope','func_scope')
class TestSomething():
    """Demo class scope fixtures."""

    def test_3(self):
        """Test using a class scope fixture."""

    def test_4(self):
        """Again, multiple tests are more fun."""

运行结果:
在这里插入图片描述
注意:fixture 只能使用同级别的其他fixture,或比自己级别更高的其他fixture,比如:函数级别的fixture可以使用同级别的,也可以使用类级别,模块级别和会话级别的fixture,但是反过来就不行。

修改fixture的作用范围

比如在测试之前进行数据连接,目前为止的示例里面都是每次测试都去连接并创建数据,我们可以把数据库操作的作用范围改成会话级别即可

@pytest.fixture(scope='session')
def demo_db(tmpdir):
    demo.test_start_demo_db()
    yield  # 运行测试函数
    demo.test_stop_demo_db()
    
@pytest.fixture(scope='session')
def demo_3_datas():
    print('返回包含3条数据的list')
    
@pytest.fixture()
def demo_db_add_3_datas(demo_db, demo_3_datas):
    for data in demo_3_datas:
        print('逐条添加demo_3_datas中的数据')
        demo.add(data)
    
def test_add_increases_count(demo_db_add_3_datas):
    # 可以使用数据库中的数据,比如断言条数是否为3
    assert demo.count() == 3

指定 fixture autouse=True

通过指定autouse=True ,使作用域内的函数都运行该 fixture,并且在同级别的测试函数中会优先执行

@pytest.fixture(autouse=True) # 设置为默认运行 
def before():     
    print("------->before") 
class Test_ABC:     
    def setup(self):         
        print("------->setup")     
    def test_a(self):         
        print("------->test_a")         
    assert 1 
if __name__ == '__main__':     
    pytest.main("-s  test_abc.py")

运行结果:

    ------->before # 发现before自动优先于测试类运行,因为autouse为 True 会默认在相同作用域下优先执行  
    ------->setup    
    ------->test_a

fixture 重命名

@pytest.fixture(name='t')
def some_tuple():
    print('some_tuple setup ')
    yield (1, 3, 5)
    print('some_tuple teardown ')
def test_some_tuple(t):
    assert some_tuple == (1, 3, 5)

可以使用pytest --fixtures .\test_fixtures.py 查看测试文件中可供使用的fixture
在这里插入图片描述

fixture 参数化

import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request):  # 传入参数request 系统封装参数
    return request.param  # 取列表中单个值,默认的取值方式

def test_a1(need_data):
    print("------->test_a")

request 是pytest内建的fixture之一,代表fixture的调用状态,它有一个param字段,会被@pytest.fixture(params=[1, 2, 3])的params列表中的一个元素填充。

运行pytest -s -v .\test_fixtures.py::test_a1输出结果:
可以发现结果运行了三次
在这里插入图片描述

对测试函数进行参数化处理,可以多次运行的知识该测试函数,但是参数化fixture使每个使用该fixture的测试函数都可以被多次运行。

指定fixture ids

ids参数也可被指定为一个函数,供pytest生成task标记

import pytest
def id_func(fixture_value):
    """A function for generating ids."""
    t = fixture_value
    return 'id_' + str(t)
    
@pytest.fixture(params=[1, 2, 3],ids=id_fuc)
def need_data(request):  # 传入参数request 系统封装参数
    return request.param  # 取列表中单个值,默认的取值方式

def test_a1(need_data):
    print("------->test_a")

运行pytest -s -v .\test_fixtures.py::test_a1输出结果:
在这里插入图片描述

;