pytest的fixture的详细使用 (更灵活高级的前/后置处理方法)(pytest.fixtrue()区别)

网友投稿 809 2022-09-01


pytest的fixture的详细使用 (更灵活高级的前/后置处理方法)(pytest.fixtrue()区别)

一、fixture基本操作介绍

虽然pytest在unittest的两组前置后置方法方法基础上,提供了更全面的总共五组的前置后置方法,但这些方法都是针对各自对应的整个作用域全局生效的,

如果有以下场景:用例 1 需要先登录,用例 2 不需要登录,用例 3 需要先登录。很显然无法用 setup 和 teardown 来实现了

pytest框架的精髓fixture可以让我们随心所欲的定制测试用例的前置后置方法

fixture是pytest将测试前后进行预备、清理工作的代码分离出核心测试逻辑的一种机制

1.基本形式和用法:

如果测试用例的参数列表中包含fixture的名字,那么pytest会根据名字检测到该fixture,并在测试函数运行之前执行该fixture

fixture可以完成测试任务,也可以返回数据给测试用例

import pytest@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)def func_01(): print("这就定义了一个简单的fixture") return '结果'def test_01(func_01): print(type(func_01), func_01) # 结果if __name__ == '__main__': pytest.main()

2.检测顺序:

检测顺序是:当前测试类 > 模块(.py文件)> 当前包中conftest.py > 父包中conftest.py > 根目录中conftest.py

3.存放位置

4.fixture调用方式

注意事项: 如果不是测试用例,无论哪种调用方式都不会生效(参考func_006) 未传入fixture,不会执行仍何fixture

1)参数传参:

将fixture函数名当参数传入用例(函数名无引号)

支持多个,支持fixture相互调用时给fixture传参

返回值:fixture执行完毕将返回值赋值给用例参数名,无返回值默认为None

2)装饰器传参:

支持多个,不支持fixture相互调用时给fixture传参

返回值:不能获取

第一种:传入名字,@pytest.mark.usefixtures("fixture1", "fixture2") (字符串格式,带引号的)

第二种:多个可以使用@pytest.mark.usefixture()进行叠加,先执行的放底层,后执行的放上层

3)autouse=True,自动调用,详见下一节: 其他参数使用

5.fixture实例化顺序

上面的规则是基本前提,当遇到不同级别fixture相互调用的情况时,实例化顺序会很复杂让人头疼:(使用需谨慎)

import pytestorder = []@pytest.fixture(scope="session")def s1(): order.append("s1")@pytest.fixture(scope="session")def s2(): order.append("s2")@pytest.fixture(scope="session")def s3(): order.append("s3")@pytest.fixture(scope="session")def s4(): order.append("s4")@pytest.fixture(scope="session")def s5(s7): order.append("s5")@pytest.fixture(scope="session")def s6(): order.append("s6")@pytest.fixture(scope="session")def s7(): order.append("s7")@pytest.fixture(scope="module")def m1(): order.append("m1")@pytest.fixture(scope="module")def m2(s5): order.append("m2")@pytest.fixture(scope="module")def m3(s4): order.append("m3")@pytest.fixturedef f1(s2, f3): order.append("f1")@pytest.fixturedef f2(m2, s3): order.append("f2")@pytest.fixturedef f3(s6): order.append("f3")def test_order(f2, f1, m3, m1, s1): print(order) # ['s1', 's3', 's2', 's4', 's7', 's5', 's6', 'm3', 'm1', 'm2', 'f2', 'f3', 'f1']if __name__ == '__main__': pytest.main()

View Code

可以先画出继承关系图,这样就会很明了,按着等级去找就对了:

在实例化基本规则大前提下,fixture可能存在不同等级之间的相互调用,这就存在依赖深度等级,如图:1.所有的session级必定最先执行,接下来是确定该级别fixture的先后顺序: 1)先运行第一等级的session级别,运行s1 2)第二等级按照传入(f2, f1, m3, m1, s1)传入顺序依次查找,得到 s3 > s2 > s4 3)第三等级按照传入(f2, f1, m3, m1, s1)传入顺序依次查找,得到 (s5依赖于同scope级别s7)s7 > s5 > s6 2.接下来运行module级别 1)第一等级按照传入顺序 m3 > m1 2)第二等级按照传入(f2, f1, m3, m1, s1)传入顺序依次查找,得到 m2 3.运行function级别 1)第一等级按照传入顺序 f2 > f3 > f1 (f1依赖于同scope级别f3)走完以上流程得到最终结果: ['s1', 's3', 's2', 's4', 's7', 's5', 's6', 'm3', 'm1', 'm2', 'f2', 'f3', 'f1']

二、scope参数详解(fixture的作用范围)

fixture里面scope参数可以控制fixture的作用范围:session > module > class > function(默认)

fixture可相互调用,但要注意:如果级别不同,低级别可以调用高级别,高级别不能调用低级别

- function:每一个调用了该fixture的函数或方法执行前都会执行一次,在测试用例执行之后运行销毁代码

- class:每一个类只调用一次,一个类中可以有多个用例调用,但是只在最早运行的用例之前调用一次(每一个类外函数用例也都会调用一次)

- module:每一个.py文件调用一次,该模块内最先执行的调用了该fixture的用例执行前运行且只运行一次

- session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module

1.scope=function 函数级(默认级别)

最基本的fixture

1.执行时机  每一个调用了该fixture的函数或方法执行前都会执行一次,在测试用例执行之后运行销毁代码2.fixture可相互调用(参考test_002的login_and_logout) 1)fixture可以像测试用例的参数传参一样调用其他的fixture,并获取其返回值 2)多个fixture的执行顺序和测试用例调用的情况是一样的   3)只支持参数传入fixture,不支持装饰器传参

代码:

import pytest@pytest.fixturedef login(): print("打开浏览器") return 'chrome'@pytest.fixture()def logout(): print("关闭浏览器")@pytest.fixture()def login_and_logout(logout, login): # fixture的相互调用,支持调用多个,执行顺序:loguot > login > login_and_logout print(f"先打开{login},又关闭了它") return f'{login} + {logout}'def test_005(login): # 第一种传参:fixture的名字作为参数传参============================================================================= print(f'005: login={login}')class TestLogin: def test_001(self, login): print(f"001: login={login}") def test_003(self, login, logout): # 支持传多个,执行顺序按照传入顺序:login > logout > test_003 print(f"003: login={login} logout={logout}") def test_002(self, login_and_logout): print(f"002: login_and_logout={login_and_logout}") def test_004(self): print("004:未传入,不会执行仍何fixture") @pytest.mark.usefixtures("login", "logout") # 第二种传参:装饰器传参,传入fixture的str,这种不能获取返回值============== def test_005(self): print(f"005: 这样传参无法获取到fixture的返回值") @pytest.mark.usefixtures("login", "logout") def func_006(self): print("006:不是测试用例,加了装饰器也不会执行fixture")if __name__ == '__main__': pytest.main()

View Code

2.scope=class 类级别

1.调用方式: 和function一样 2.运行时机: 1)类外的独立函数用例执行前会执行一次,参考test_006 2)类中有用例调用了fixture,则会在最先调用的那个用例执行前执行一次(参考test_002),该类下其他调用了fixture的用例不再执行(参考test_003) 3)未调用该fixture的类和函数,不会运行 2.fixture相互调用规则 1)类级可以调用类级,参考test_004 2)函数级可以调用类级,参考test_008 3)类级不可以调用函数级,参考test_007

代码:

import pytest@pytest.fixture(scope='class')def login(): print("打开浏览器 -- 类级别fixture") return 'chrome'@pytest.fixture()def logout(): print("关闭浏览器 -- 函数级fixture")@pytest.fixture(scope='class')def class_use_class(login): print(f"class_use_class -- 类级可以调用类级") return f'{login}'@pytest.fixture()def function_use_class(login): print(f"function_use_class -- 函数级调用类级")@pytest.fixture(scope='class')def class_use_function(logout): print(f"错误示范,类级不可以调用函数级")def test_006(login): print(f'006: login={login}')class TestLogin: def test_001(self, logout): print(f"001: login={logout} 调用普通函数级") def test_002(self, login): print(f"002:logout={login} 调用类级,该类中login只会在这里运行一次") def test_003(self, login, logout): print(f"003: login={login} logout={logout} 调用类级和函数级,该类中类级login不会再运行") @pytest.mark.usefixtures("class_use_class") def test_004(self, class_use_class): print(f"004: class_use_class 调用类级-->类再调用类级login,运行过了不会再运行") # def test_004(self, class_use_class): # print(f"004: class_use_class={class_use_class} 调用类级-->类再调用类级login,运行过了不会再运行") def test_007(self, class_use_function): print(f"007: class_use_function={class_use_function} 错误示范,类级不可以调用函数级") def test_008(self, function_use_class): print(f"008: function_use_class 调用函数级-->函数级再调用类级login,运行过了不会再运行") def test_005(self): print(f"005: 未传入任何fixture,哪个级别都与我无关")class TestNoFixture: def test_009(self): print('009:未传入任何fixture,哪个级别都与我无关')if __name__ == '__main__': pytest.main()

View Code

3.scope=module 模块级

1.调用方式: 和function一样2.运行时机: 每一个.py文件调用一次,该模块内最先执行的调用了该fixture的用例执行前运行且只运行一次(如test_006 + test_001 + test_003)2.fixture相互调用规则 1)类级可以调用模块级,参考test_005 2)函数级可以调用模块级,参考test_008 3)模块级不可以调用函数级,和类级参考 test_004 test_007

代码:

import pytest@pytest.fixture(scope='module')def open(): print("打开电脑 -- 模块级别fixture") return 'windows'@pytest.fixture(scope='class')def login(): print("打开浏览器 -- 类级别fixture") return 'chrome'@pytest.fixture()def logout(): print("关闭浏览器 -- 函数级fixture")@pytest.fixture(scope='module')def module_use_class(login): print(f"错误示范,模块级不可以调用类级") return f'{login}'@pytest.fixture(scope='module')def module_use_func(logout): print(f"错误示范,模块级不可以调用函数级") return f'{logout}'@pytest.fixture()def function_use_module(open): print(f"function_use_module -- 函数级调用模块级")@pytest.fixture(scope='class')def class_use_module(open): print(f"class_use_module -- 类级调用模块级")def test_006(login): print(f'006: login={login}')class TestLogin: def test_001(self, open): print(f"001: open={open} 调用模块级") def test_002(self, login): print(f"002:login={login} 调用类级") def test_003(self, open, login, logout): print(f"003: open={open} login={login} logout={logout} 调用模块级、类级和函数级") @pytest.mark.usefixtures("module_use_class") def test_004(self): print(f"004: module_use_class 错误示范,模块级不能调用类级") def test_007(self, module_use_func): print(f"007: module_use_func 错误示范,模块级不能调用函数级 ") def test_008(self, function_use_module): print(f"008: function_use_module") def test_005(self, class_use_module): print(f"005: class_use_module")class TestNoFixture: def test_009(self): print('009:未传入任何fixture,哪个级别都与我无关')if __name__ == '__main__': pytest.main()

View Code

4.scope=session

fixture为session级别是可以跨.py模块调用的,运行一次程序只会调用一次

也就是当我们有多个.py文件的用例的时候,如果多个用例只需调用一次fixture,那就可以设置为scope="session",并且写到conftest.py文件里作为全局的fixture

放到项目的根目录下就可以全局调用了,如果放到某个package下,那就在该package内有效

三、其他参数介绍

1.params

一个可选的参数列表,它将导致被fixture装饰的测试用例以列表中每个列表项为参数,多次调用fixture功能

1.fixture可以带参数,params支持列表;

2.默认是None;

3.对于param里面的每个值,fixture都会去调用执行一次,就像执行for循环一样把params里的值遍历一次。

在 pytest 中有一个内建的 fixture 叫做 request,代表 fixture 的调用状态。request 有一个字段 param,使用类似​​@pytest.fixture(param=tasks_list)​​​的方式给fixture传参,在 fixture 中使用 ​​request.param​​的方式作为返回值供测试函数调用。其中 tasks_list 包含多少元素,该 fixture 就会被调用几次,分别作用在每个用到的测试函数上

如下fixture和测试用例都执行三遍相当三个测试用例,场景如:测试三组账户密码登录,登陆用例都是一个只是每次数据不同

import pytesttasks_list = [(10,11),(20,22),(33,33)]@pytest.fixture(params=tasks_list)def test_data(request): print(f"fixture得到:账号:{request.param[0]},密码:{request.param[1]}" ) return request.paramclass TestData: def test_1(self,test_data): print("用例:",test_data)if __name__ == '__main__': pytest.main()

2.ids

ids通常可以与params一起使用,在没有指定 id情况下,在输出时 pytest 会自动生成一个标识作为测试ID:

当params列表项是数字、字符串、布尔值和None时,将使用列表项自身字符串形式表示测试ID,如[True1] [True2] [xxoo] [123] [None]等对于其他对象,pytest会根据参数名称创建一个字符串,如params中截图显示的[test_data0] [test_data1]可以通过使用​​ids​​关键字参数来自定义用于指定测试ID,例如@pytest.fixture(param=tasks_list,ids=task_ids) , ids可以是列表,也可以是函数供 pytest 生成 task 标识。

import pytestdata_list = [(10,11),(20,22),(33,33)]@pytest.fixture(params=data_list, ids=["a","b","c"])def tes_data(request): print(f"fixture得到:账号:{request.param[0]},密码:{request.param[1]}" ) return request.paramclass TestData: def test_1(self,tes_data): print("用例:",tes_data)if __name__ == '__main__': pytest.main()

3.autouse

默认False不开启

当用例很多的时候,每次都传fixture,会很麻烦。fixture里面有个参数autouse,默认是False没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了

autouse设置为True,自动调用fixture功能,无需传仍何参数,作用范围跟着scope走(谨慎使用)

import pytest@pytest.fixture(scope='module', autouse=True)def test1(): print('开始执行module')@pytest.fixture(scope='class', autouse=True)def test2(): print('开始执行class')@pytest.fixture(scope='function', autouse=True)def test3(): print('开始执行function')def test_a(): print('---用例a执行---')def test_d(): print('---用例d执行---')class TestCase: def test_b(self): print('---用例b执行---') def test_c(self): print('---用例c执行---')

自动使用fixture

========================================================================================================================= test session starts ==========================================================================================================================platform win32 -- Python 3.8.0, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- E:\python3.8\python.execachedir: .pytest_cacherootdir: D:\代码\自动化测试\pytest_test, configfile: pytest.ini, testpaths: ./dir01/dir01_test.pycollected 4 items dir01/dir01_test.py::test_a开始执行module开始执行class开始执行function---用例a执行---PASSEDdir01/dir01_test.py::test_d开始执行class开始执行function---用例d执行---PASSEDdir01/dir01_test.py::TestCase::test_b开始执行class开始执行function---用例b执行---PASSEDdir01/dir01_test.py::TestCase::test_c开始执行function---用例c执行---PASSED================================================================================================================================ PASSES ================================================================================================================================

执行结果

4.name

fixture的重命名默认为 fixture 装饰的的函数名,但是 pytest 也允许将fixture重命名如果使用了name,只能将name传入,函数名不再生效

四、fixture的teardown后置操作

1.使用yield实现

前几章中都是前置操作,后置操作需要用到python的 yield来实现,yield语法讲解点这里:>>迭代器生成器<<

如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容如果测试用例抛出异常,yield后面的teardown内容还是会正常执行

import pytest@pytest.fixture(scope="session")def open(): # 整个session前置操作setup print("===打开浏览器===") test = "测试变量是否返回" yield test # 整个session后置操作teardown print("==关闭浏览器==")@pytest.fixturedef login(open): # 方法级别前置操作setup print(f"输入账号,密码先登录{open}") name = "==我是账号==" pwd = "==我是密码==" age = "==我是年龄==" # 返回变量 yield name, pwd, age # 方法级别后置操作teardown print("登录成功")def test_s1(login): print("==用例1==") # 返回的是一个元组 print(login) # 分别赋值给不同变量 name, pwd, age = login print(name, pwd, age) assert "账号" in name assert "密码" in pwd assert "年龄" in agedef test_s2(login): print("==用例2==") print(login)if __name__ == '__main__': pytest.main()

2.使用request.addfinalizer终结函数实现

yield是返回数据并暂停,后置操作在yield后面。

终结函数是用return返回,终结函数的后置操作处于前置和return中间。

import pytest@pytest.fixture(scope="module")def test_addfinalizer(request): # 前置操作setup print("==打开浏览器==") test = "test_addfinalizer" def fin(): # 后置操作teardown print("==关闭浏览器==") request.addfinalizer(fin)# 返回前置操作的变量 return testdef test_anthor(test_addfinalizer): print("==最新用例==", test_addfinalizer)if __name__ == '__main__': pytest.main()

辉辉辉辉a

小菠萝

全栈测试开发日记


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:pytest和unittest的关系(unittest和pytest区别)
下一篇:Mybatis分页查询的实现(Rowbounds和PageHelper)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~