python-pytest使用(2)-fixture
原创: George555 公众号:测试江湖驿站
如果这些内容对你有帮助,也可以打开微信扫一扫,加关注:
1.fixture和setup/teardown的区别:
-
setup/teardown是针对测试脚本全局来设置的,没办法只针对某个方法来设置,假如遇到一些用例1需要提前登陆,用例3不需要提前登陆,到了用例5又要登陆,这种场景的用setup就显得很无助了,这时就可用fixture的方式替换setup/teardown了
-
fixture的命名方式很灵活,不像setup/teardown,只能用如setup、teardown、setup_method等;
-
使用fixture的conftest方式可以跨模块(py文件)来共享前置、后置数据,而且在调用时不需要导入fixture修饰的方法,即:scope="module"方式
-
fixture还可以实现多个模块-py文件共用一个session的情况,即:scope=session方式
2. fixture方法中参数有哪些:
def fixture(scope="function", params=None, autouse=False, ids=None, name=None)
在程序中使用时在方法或类前加:@pytest.fixture(scope, params, autouse, ids, name),其他方法调用这个装饰器时,只需要在参数中加入装饰器方法名即可(无需导入此装饰的方法)
示例:
import pytest
@pytest.fixture(scope="function",
params=([{"id":10,"name":"mike"},
{"id":20,"name":"bill"}]))
def funGlobal(request):
'''被装饰方法'''
id=request.param['id']
name=request.param['name']
# print("这里是funGlobal()")
return id,name
def test_case1(funGlobal):
'''测试方法,入参直接调用即可,如果测试方法和被装饰方法不再一个py文件,
则无需导入被修饰方法'''
print("这里是测试用例1")
3.fixture方法中各个参数详解:
3.1 scope参数有四种,分别是'function','module','class','session',默认为function。
a.function:每个test都运行,不带参数默认是function,---->所有文件里的测试用例执行前都执行1次fixture装饰器修饰的方法
注意:py文件(即模块)内依次执行所有测试方法(包括类内和类外)
b.class:每个class的所有test只运行一次,---->每个测试文件里的测试类执行前执行1次fixture装饰器修饰的方法,类外测试方法不考虑
注意:py文件(即模块)内依次执行所有测试方法(包括类内和类外),若多个测试方法调用同1个装饰器方法,则执行时合并到一轮装饰器传参内,即案例中case3、case4
c.module:每个module的所有test只运行一次,可以实现多个.py跨文件共享, 每一个.py文件调用一次,---->每个测试py文件执行前执行1次conftest文件中的fixture
注意:py文件(即模块)内优先依次执行带fixture装饰的测试方法(包括类内和类外),再依次执行不带fixture的方法。,若多个测试方法调用同1个装饰器方法,则执行时合并到一轮装饰器传参内,即案例中case1、case3、case4
d.session:每个session只运行一次,可以实现多个.py跨文件使用1个session来完成多个用例,session作用域是最大的,常配合conftest.py方式使用---->所有测试文件执行前执行1次conftest文件中的fixture
e.案例代码如下:
# -*- coding:utf-8 -*-
'''
Created on 2019年8月6日
@author: George
示例:pytest.fixture在当前py文件内 各种位置混合使用
'''
import pytest
@pytest.fixture(scope="module",
params=([{"id":1,"name":"mike"},
{"id":2,"name":"bill"}]),name="testfun")
def funGlobal(request):
'''fixture方法,request是固定参数'''
id=request.param['id'] #request.param可以拿到装饰器的params的值
name=request.param['name']
print("funGlobal()--别名testfun")
return id,name
def noCase():
print("这里不是测试用例方法")
def testcase1(testfun):
'''1.类外测试方法,调用fixture装饰器方法'''
print("\n testcase1--{}".format(testfun))
def testcase2():
'''2.类外测试方法,不调用fixture'''
print("testcase2()\n")
'''3.放在class类前,相当于类内所有测试方法都带入fixture装饰方法,
就不用每个单独加@pytest.mark.usefixtures("testfun") '''
class TestClass():
def testcase11(self):
'''4.类内测试方法,不调用fixture'''
print("testcase11")
@pytest.mark.paramTest #打mark标签
def testcase3(self,testfun):
'''5.类内测试方法,入参传fixture装饰方法'''
print("testcase3----id:{}".format(testfun))
def testcase4(self):
'''6.类内测试方法,使用pytest.mark.usefixtures
调用fixture装饰方法(此处不是打mark标签)'''
print("testcase4")
if __name__=="__main__":
pytest.main(["-s", __file__])
下来看下对于上面的代码,当scope为四种不同方式时的输出:
######使用scope="function":针对模块内所有测试方法#######
test_fixtureDemo2.py funGlobal()--别名testfun
testcase1--(1, 'mike')
.funGlobal()--别名testfun
testcase1--(2, 'bill')
.testcase2()
.testcase11
.funGlobal()--别名testfun
testcase3----id:(1, 'mike')
.funGlobal()--别名testfun
testcase3----id:(2, 'bill')
.funGlobal()--别名testfun
testcase4
.funGlobal()--别名testfun
testcase4
说明:py文件内,从上到下一次执行测试方法,遇到有装饰器的直接调用装饰器
#####使用scope="class":######
注:因为case3和case4都调用testfun了,所以执行时合并到了
一轮传参的testfun装饰器范围内
test_fixtureDemo2.py funGlobal()--别名testfun
testcase1--(1, 'mike')
.funGlobal()--别名testfun
testcase1--(2, 'bill')
.testcase2()
.testcase11
.funGlobal()--别名testfun
testcase3----id:(1, 'mike')
.testcase4
.funGlobal()--别名testfun
testcase3----id:(2, 'bill')
.testcase4
说明:py文件内,先在类外从上到下执行带装饰器名的测试用例,然后执行不带装饰器名的测试用例;再进入类中从上到下执行测试用例,遇到多个带相同装饰器的测试方法先执行装饰器再执行多个测试方法
######使用scope="module":#######
test_fixtureDemo2.py funGlobal()--别名testfun
testcase1--(1, 'mike')
.testcase3----id:(1, 'mike')
.testcase4
.funGlobal()--别名testfun
testcase1--(2, 'bill')
.testcase3----id:(2, 'bill')
.testcase4
.testcase2()
.testcase11
说明:先执行有装饰器方法的测试用例按从上到下执行,在执行不带装饰器的测试用例
当scope="session"这里单独写个例子,针对常见的登录业务
import pytest
@pytest.fixture(scope="session",
params=([{"username":"mike","password":"123456"},
{"username":"bill","password":"222222"}]),
name="login")
def funLogin(request):
'''fixture方法,request是固定参数'''
print('scope=session开始了。。。')
username=request.param['username']
password=request.param['password']
print("funGlobal()--别名testfun")
yield username,password #yield是销毁操作,见2.4
print ('scope=session结束了。。。')
def testcase2():
'''2.不需要登录验证'''
print("testcase2()\n")
def testcase1(login):
'''1.需要登录验证'''
print("\n testcase1登录用户名:{} 登录密码:{}".format(login[0],login[1]))
assert login[0] in ['mike','bill'],'用户名不存在'
def testcase3(login):
'''需要登录用户名验证-用户名不存在'''
print("\n testcase3登录用户名:{} 登录密码:{}".format(login[0],login[1]))
assert login[0] in ['mike','tina'],'用户名不存在'
def testcase4(login):
'''需要登录密码验证-密码错误'''
print("\n testcase4登录用户名:{} 登录密码:{}".format(login[0],login[1]))
assert login[1]=='123456','密码错误'
if __name__=="__main__":
pytest.main(["-s", __file__,"--html=fixturereport.html"])
上面代码输出:
test_fixtureDemo3.py testcase2()
.scope=session开始了。。。funGlobal()--别名testfun
testcase1登录用户名:mike 登录密码:123456
testcase3登录用户名:mike 登录密码:123456
testcase4登录用户名:mike 登录密码:123456
.scope=session结束了。。。scope=session开始了。。。funGlobal()--别名testfun
testcase1登录用户名:bill 登录密码:222222
testcase3登录用户名:bill 登录密码:222222
Ftestcase4登录用户名:bill 登录密码:222222
Fscope=session结束了。。。后面是断言相关输出信息......
说明:py文件内,scope=session的时候,所有用例在开始会执行1次fixture,然后全部用例结束后再执行1次fixture中的销毁操作。用例按顺序一次执行。因有2组数据 所以执行2次。
3.2 params:可选参数,默认None,可传入多个参数来多次调用fixture装饰的方法,在用例中分别循环使用这些不同参数。
3.3 autouse:为True,则为所有测试激活fixture,对于测试方法可以看到它;为False(默认值)则显示需要参考来激活fixture
注意:若为True,则在测试方法中调用装饰器方法时,测试方法不需要在参数中指定对应的被装饰函数名也会被调用;若为False即默认值,则在测试方法的参数中需要传入被装饰的方法名或装饰器别名。可见autouse=True会影响所有测试方法,使用时要慎重。
3.4 ids:每个字符串id的列表,每个字符串对应于params,这样他们就是测试ID的一部分。如果没有提供ID,将从params自动生成
3.5 name:fixture的名称。默认为装饰函数的名称。如果fixture在它的同一模块中使用,则功能名称将被请求的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名为“fixture_ <fixturename>”然后使用”@ pytest.fixture(name ='<fixturename>')“”
4. fixture的多种使用方式汇总:
方式1:在测试方法的参数中传入fixture装饰函数名
import pytest
@pytest.fixture(scope="function",
params=([{"id":10,"name":"Demo8-mike"},
{"id":20,"name":"Demo8-bill"}]),name="testfun")
def funGlobal(request):
'''fixture方法1-作用域为:function'''
id=request.param['id']
name=request.param['name']
print("这里是装饰器funGlobal()别名testfun")
return id,name
def testcase1(testfun):#将装饰函数fixture以别名testfun传入测试用例方法的参数中
print("\n这里是类外部方法testcase1,调用了funGlobal()====={}".format(testfun))
方式2:测试方法前用@pytest.mark.usefixtures("装饰函数名")调用fixture
注:
a.这里可以放在包中即类之外的方法前;也可以在类内测试方法前,也可放在类名前,放在类前是对类内所有测试方法起作用。
b.如果class用例需要同时调用多个fixture,可以使用@pytest.mark.usefixtures()叠加。注意叠加顺序,先执行的放底层,后执行的放上层
c.如果fixture装饰器有返回值,那么用usefixtures就无法获取到返回值,这个便是usefixtures和用例参数中直接传装饰器名的区别。也就是说当装饰器fixture的return值需要使用时,只能在用例里面传fixture参数了;当用例中不需要装饰器fixture的return值的时候,两种方法都可以
import pytest
@pytest.fixture(scope="function",
params=([{"id":10,"name":"Demo8-mike"},
{"id":20,"name":"Demo8-bill"}]),name="testfun")
def funGlobal(request):
'''fixture方法1-作用域为:function'''
id=request.param['id']
name=request.param['name']
print("这里是装饰器funGlobal()别名testfun")
return id,name
@pytest.mark.usefixtures("testfun") #调用testfun装饰器方法,而非mark标签方式
def testcase4():
#testcase4 用了usefixtures打标签装饰,则运行时,有几个参数,就运行几次
print("这里是类内部方法testcase4")
方式3:使用autouse的方式调用fixture,见2.2中c部分提到,autouse=Ture是当前py文件全局使用装饰函数,=False则需要在对应测试方法的参数中传入装饰函数名,默认是False
# -*- coding:utf-8 -*-
'''
示例:pytest.fixture-autouse的使用
此示例是针对autouse=True的,=False的见方式2即可
'''
import pytest
@pytest.fixture(scope="function",autouse=True,
params=([{"id":1,"name":"mike"},
{"id":2,"name":"bill"}]),name="testfun")
def funGlobal(request):
'''fixture方法,request是固定参数'''
id=request.param['id'] #request.param可以拿到装饰器的params的值
name=request.param['name']
print("funGlobal()--别名testfun")
return id,name
def noCase():
print("这里不是测试用例方法")
def testcase1():
print("testcase1")
def testcase2():
'''类外测试方法,不调用fixture'''
print("testcase2")
class TestClass():
def testcase3(self):
print("testcase3")
def testcase4(self):
print("testcase4")
if __name__=="__main__":
pytest.main(["-s", __file__])
'''输出:因为params有两组参数,所以每个测试方法都运行了2次
test_fixtureDemo2.py funGlobal()--别名testfun
testcase1
.funGlobal()--别名testfun
testcase1
.funGlobal()--别名testfun
testcase2
.funGlobal()--别名testfun
testcase2
.funGlobal()--别名testfun
testcase3
.funGlobal()--别名testfun
testcase3
.funGlobal()--别名testfun
testcase4
.funGlobal()--别名testfun
testcase4
.
'''
5. 拆卸代码--即销毁操作,类似teardown方式
pytest中如果用了fixture,其实就不需要再用setup/teardown之类的初始化、销毁功能了,而在pytest中的teardown功能怎么写呢?一般用到两种方式:yield 和 addfinalizer
A.yield:关键字,在fixture中代替teardown功能
在上面3.1的e中scope=session中已经看到此方法使用。常见yield使用场景有3种:
-
独自使用,则后续代码则会每次销毁执行的功能,如下代码:
@pytest.fixture()
def funfixture():
print("执行fixture前面初始化部分")
yield
print("执行fixture后面销毁部分")
-
会面加参数,则用作fixture装饰器的返回值,后续代码依然做销毁执行的功能
B.addfinalizer:功能同yield一样,区别是可以在销毁操作中加入不同方法/函数,可以加return
import pytest
@pytest.fixture(params=([{"username":"mike","password":"123456"},
{"username":"bill","password":"222222"}]))
def funLogin(request):
print('初始化开始了。。。')
assert 1==1
def myteardown():
print("这里是销毁操作teardown,后续开始销毁")
request.addfinalizer(myteardown)
return request.param
def testcase2(funLogin):
print("testcase2()\n",funLogin)
if __name__=="__main__":
pytest.main(["-s", __file__])
上面代码输出:
test_fixtureDemo6.py 初始化开始了。。。testcase2()
{'username': 'mike', 'password': '123456'}
.这里是销毁操作teardown,后续开始销毁
初始化开始了。。。testcase2()
{'username': 'bill', 'password': '222222'}
.这里是销毁操作teardown,后续开始销毁
特别注意:
A.如果要在测试方法中使用装饰函数的params参数值,那么在装饰函数中必须得有返回值,这是就需要在yield后加返回参数名了,如:yield id,name ,调用装饰函数后,调用装饰函数名,就可以拿到id、name相关信息。
B.使用yield后,加入多个测试用例中有个别用例有异常或断言失败,则yield之后功能不受影响;若yield之前初始化部分出现异常或断言失败,则yield后代码不再执行。
import pytest
@pytest.fixture(params=([{"username":"mike","password":"123456"},
{"username":"bill","password":"222222"}]))
def funLogin(request):
print('初始化开始了。。。')
assert 1==2
yield
print ('销毁操作结束了。。。')
def testcase2(funLogin):
print("testcase2()\n",funLogin)
代码输出: 由输出可见fixture里初始化断言失败,则所有用例和yield之后的代码都不运行
test_fixtureDemo5.py 初始化开始了。。。E
初始化开始了。。。E
后续是断言信息.......
C.yield也可以配合with语句使用
# 这里放一个官方案例
@pytest.fixture(scope="module")
def smtp():
with smtplib.SMTP("smtp.gmail.com") as smtp:
yield smtp # provide the fixture value
D.总结:
yield和addfinalizer方法都可以作为测试最后的调用对应代码功能来使用。但使用addfinalizer有些区别的,比如:A.可以注册多个终结函数;B.可以加return关键字等。
文中为自己学习时笔记记录,有不恰当之处,欢迎大家指正,进行修改。
#####欢迎大家加群交流####
QQ:464314378
微信群请先加群主微信,群主会拉各位小伙伴进群,注意添加备注。