pytest进阶教程之fixture函数详解


Posted in Python onMarch 29, 2021

fixture函数存在意义

与python自带的unitest测试框架中的setup、teardown类似,pytest提供了fixture函数用以在测试执行前和执行后进行必要的准备和清理工作。但是相对来说又比setup、teardown好用。

firture相对于setup和teardown的优势

  1. 命名方式灵活,不局限于setup和teardown这几个命名
  2. conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置
  3. scope="module" 可以实现多个.py跨文件共享前置, 每一个.py文件调用一次
  4. scope="session" 以实现多个.py跨文件使用一个session来完成多个用例

fixture函数定义

通过将fixture声明为参数名,测试用例函数可以请求fixture。fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。

# 定义的夹具函数,使用装饰器pytest.fixture
@pytest.fixture
def my_fruit():
 print("login:用户执行登录操作")
 
# 使用夹具函数的测试用例
def test_my_fruit_in_basket(my_fruit):
 print("hello world")
 
if __name__ == '__main__':
 pytest.main(['test_login.py::test_my_fruit_in_basket', '-s'])
 
#执行结果:
collected 1 item
test_login.py login:
用户执行登录操作
hello world
.
============================== 1 passed in 0.02s ==========================

fixture作用

  • 做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现。
  • 测试用例的前置条件可以使用fixture实现 。
  • 支持经典的xunit fixture ,像unittest使用的setup和teardown。
  • fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题。

调用fixture有三种方式

Fixture名字作为测试用例的参数

可以直接使用fixture名称作为输入参数(是个典型的高阶函数),在这种情况下,fixture函数返回的fixture实例将被注入,最终在测试用例执行前执行这个装饰过的函数。如下列代码,①将返回值传递给测试用例,②通过函数入参方式,可以传入多个fixture函数

import pytest
 
@pytest.fixture
def first_entry():
 return "a"
@pytest.fixture
def order(first_entry):
 return [first_entry]
def test_string(order):
 order.append("b")
 assert order == ["a", "b"], "断言执行失败"
 
if __name__ == '__main__':
 pytest.main(['test_login.py::test_string', '-s'])

使用@pytest.mark.usefixtures('fixture')装饰器

每个函数或者类前使用@pytest.mark.usefixtures('fixture')装饰器进行装饰。

import pytest
@pytest.fixture
def my_fruit():
 print("login:用户执行登录操作")
 
# 被夹具函数装饰的测试用例
@pytest.mark.usefixtures("my_fruit")
def test_my_fruit_in_basket():
 print("hello world")
 
 
if __name__ == '__main__':
 pytest.main(['test_login.py', '-s', '-q'])
  
# 执行结果
login:用户执行登录操作 
hello world 
. 
1 passed in 0.01s

使用autouse参数

指定fixture的参数autouse=True这样模块内的每个测试用例会自动调用fixture。

import pytest
@pytest.fixture(autouse=True)
def my_fruit():
 print("login:用户执行登录操作")
 
# 被夹具函数装饰的测试用例
def test_my_fruit_in_basket():
 print("hello world")
 
 
if __name__ == '__main__':
 pytest.main(['test_login.py', '-s', '-q'])

备注: 如果fixture有返回值,那么usefixture以及autouse就无法获取到返回值,这个是装饰器usefixture与用例直接传fixture参数的区别。 因此最常用的是通过参数传递的方法。

指定Fixture函数的作用范围

Fixture中的scope的参数,控制Fixture函数的作用范围

scope = ‘function' 测试函数维度,默认范围,则在测试结束时销毁fixture。

scope = ‘class' 测试类维度,在class中最后一次测试的拆卸过程中,夹具被破坏。

scope = ‘module' 测试文件维度,在模块中最后一次测试的拆卸过程中,夹具被破坏。

scope = ‘session' 测试会话维度,夹具在测试会话结束时被销毁。

fixture函数的返回值:return 和 yield 和 addfinalizer终结函数

return:

通过下面的代码,我们已经发现可以通过测试用例函数传入参数的形式,直接使用fixture函数的返回值,这个相对来说比较简单。

import pytest
 
@pytest.fixture
def first_entry():
 return "a"
@pytest.fixture
def order(first_entry):
 return [first_entry]
def test_string(order):
 order.append("b")
 assert order == ["a", "b"], "断言执行失败"
  
if __name__ == '__main__':
 pytest.main(['test_login.py::test_string', '-s'])

yield:

yeild也是一种函数的返回值类型,是函数上下文管理器,使用yield被调fixture函数执行遇到yield会停止执行,接着执行调用的函数,调用的函数执行完后会继续执行fixture函数yield关键后面的代码。因此利用fixture函数,我们可以说pytest集合了setup、teardown,既做了初始化,又做了后置的清理工作。

import pytest
from emaillib import Email, MailAdminClient
 
@pytest.fixture
def mail_admin():
 return MailAdminClient()
 
# 配置发送者的fixture函数
@pytest.fixture
def sending_user(mail_admin):
 user = mail_admin.create_user() #setup:创建发件人
 yield user      # 返回发件人
 admin_client.delete_user(user) #teardown:删除发件人
 
# 配置收件人的fixture函数
@pytest.fixture
def receiving_user(mail_admin):
 user = mail_admin.create_user() #setup:创建收件人
 yield user      #返回收件人
 admin_client.delete_user(user) #teardown:删除收件人
 
def test_email_received(sending_user, receiving_user, email):
 email = Email(subject="Hey!", body="How's it going?")
 sending_user.send_email(email, receiving_user)
 assert email in receiving_user.inbox

项目中的实际使用

翻译下面代码,在调用Entry_into_index前,启动APP,遇到yield关键字,中止fixture函数调用,执行调用函数Entry_into_index内容,在Entry_into_index函数调用后,执行yield函数后的driver.close_app(),关闭APP。

@pytest.fixture(scope='session')
def startApp_fixture(start_app):
 driver = start_app
 res = lp(driver).get_agree_info()
 try:
  assert res == "同意"
 except Exception as e:
  log.error("启动APP失败")
  log.exception(e)
  raise e
 else:
  lp(driver).click_agree()
  lp(driver).click_next_step()
  lp(driver).click_alert()
  lp(driver).click_pass()
  # 创建首页
  index_page = indexPage(driver)
  yield index_page, driver
  # 后置条件
  time.sleep(3)
  driver.close_app()
   
# 调用fixture函数
@pytest.fixture(scope='session')
def Entry_into_index(startApp_fixture)
 index_page = startApp_fixture()[0]
 driver = startApp_fixture()[1]

fixture函数需要传递参数

工厂作为固定装置:可以使用闭包,通过外部去调用函数里面函数。

工厂固定装置原因:

上面已经说过,调用fixture函数A可以通过用fixture名称作为调用函数B参数,在这种情况下,fixture函数返回的fixture实例将被注入,最终在测试用例B执行前执行这个装饰过的函数def B(A):pass。但是有个问题在给测试用例添加装饰函数时,传入的参数是fixture函数的函数名,如果需要给fixture函数添加参数时,是不可以用下面形式,代码会直接报错。原因是测试用例传入参数为fixture函数名,如果fixture函数名添加(参数)后,表现形式为add(params)实际为函数调用。可参考高阶函数与装饰器,并无此用法。

pytest进阶教程之fixture函数详解

解决方式使用闭包,如下图代码:make_customer_record函数返回的是内部函数_make_customer_record(夹具不直接返回数据,而是返回一个生成数据的函数),注意此处未加(),非函数调用,因此在测试用例中customer_1 = make_customer_record("Lisa")此处可拆解为两部分,customer_1 = make_customer_record的结果为_make_customer_record对象 ,加上("Lisa") 实际是对调_make_customer_record函数进行调用:函数名+(参数),以达到可以传参的目的。

@pytest.fixture
def make_customer_record():
 def _make_customer_record(name):
  return {"name": name, "orders": []}
 
 return _make_customer_record #注意此处不加(),非函数调用
 
def test_customer_records(make_customer_record):
 customer_1 = make_customer_record("Lisa")

 

Python 相关文章推荐
python 计算数组中每个数字出现多少次--“Bucket”桶的思想
Dec 19 Python
Python编写一个优美的下载器
Apr 15 Python
python url 参数修改方法
Dec 26 Python
Python实现SQL注入检测插件实例代码
Feb 02 Python
python隐藏终端执行cmd命令的方法
Jun 24 Python
python使用装饰器作日志处理的方法
Jul 11 Python
python区分不同数据类型的方法
Oct 14 Python
使用 Python 处理3万多条数据只要几秒钟
Jan 19 Python
python解释器pycharm安装及环境变量配置教程图文详解
Feb 26 Python
django admin 根据choice字段选择的不同来显示不同的页面方式
May 13 Python
Python使用Selenium模拟浏览器自动操作功能
Sep 08 Python
查找适用于matplotlib的中文字体名称与实际文件名对应关系的方法
Jan 05 Python
python中pandas.read_csv()函数的深入讲解
Mar 29 #Python
python编写函数注意事项总结
Mar 29 #Python
python基于tkinter制作无损音乐下载工具
Python requests库参数提交的注意事项总结
Python爬虫爬取全球疫情数据并存储到mysql数据库的步骤
Python爬虫数据的分类及json数据使用小结
Mar 29 #Python
python re模块和正则表达式
Mar 24 #Python
You might like
PHP更新购物车数量(表单部分/PHP处理部分)
2013/05/03 PHP
php 变量引用与变量销毁机制详细介绍
2016/12/05 PHP
微信公众号开发之获取位置信息php代码
2018/06/13 PHP
PHP+iframe模拟Ajax上传文件功能示例
2019/07/02 PHP
学习YUI.Ext 第二天
2007/03/10 Javascript
使一个函数作为另外一个函数的参数来运行的javascript代码
2007/08/13 Javascript
Aptana调试javascript图解教程
2009/11/30 Javascript
jquery 学习之二 属性相关
2010/11/23 Javascript
jQuery弹出框代码封装DialogHelper
2015/01/30 Javascript
JavaScript实现获取dom中class的方法
2015/02/09 Javascript
JS实现图片产生波纹一样flash效果的方法
2015/02/27 Javascript
运行Node.js的IIS扩展iisnode安装配置笔记
2015/03/02 Javascript
JS+CSS简单树形菜单实现方法
2015/09/12 Javascript
jquery 实现输入邮箱时自动补全下拉提示功能
2015/10/04 Javascript
不间断循环滚动效果的实例代码(必看篇)
2016/10/08 Javascript
js将table的每个td的内容自动赋值给其title属性的方法
2016/10/13 Javascript
微信小程序实现自定义底部导航
2020/11/18 Javascript
Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍
2014/06/10 Python
python批量提交沙箱问题实例
2014/10/08 Python
Python通过select实现异步IO的方法
2015/06/04 Python
用Python写冒泡排序代码
2016/04/12 Python
Python多层嵌套list的递归处理方法(推荐)
2016/06/08 Python
Python入门_浅谈字符串的分片与索引、字符串的方法
2017/05/16 Python
tensorflow的ckpt及pb模型持久化方式及转化详解
2020/02/12 Python
Python sep参数使用方法详解
2020/02/12 Python
python itsdangerous模块的具体使用方法
2020/02/17 Python
Linux如何修改文件和文件夹的权限
2013/09/05 面试题
新郎新娘婚礼答谢词
2014/01/11 职场文书
2014年幼儿园元旦活动方案
2014/02/13 职场文书
需求分析说明书
2014/05/09 职场文书
船舶工程技术专业求职信
2014/08/07 职场文书
2014幼儿园卫生保健工作总结
2014/12/05 职场文书
人事行政主管岗位职责
2015/04/09 职场文书
Axios取消重复请求的方法实例详解
2021/06/15 Javascript
springboot集成springCloud中gateway时启动报错的解决
2021/07/16 Java/Android
JS setTimeout与setInterval的区别
2022/04/20 Javascript