fixture主要用于测试函数传参和前置后置操作

 

一,fixture当参数传入

 

fixture可以当做参数传入,定义fixture跟定义普通函数差不多,唯一区别就是在函数上加个装饰器@pytest.fixture()
fixture命名不要以test开头,跟用例区分开
fixture是有返回值的,没有返回值默认为None
用例调用fixture的返回值,直接就是把fixture的函数名称当做变量名称。

@pytest.fixture()
def creat_id():
a=2
return a*3

def test_compute(creat_id):
some=creat_id+4
assert some==11

 

creat_id = 6

def test_compute(creat_id):
some=creat_id+4
> assert some==11
E assert 10 == 11
test_study.py:42: AssertionError

 

在这里故意用例执行失败,通过回溯可以看到
1,test_compute测试函数需要一个名为的函数参数creat_id,通过查找名为的带有fixture标记的函数,可以找到匹配的夹具函数creat_id
2,creat_id() 被称为创建实例,得到creat_id=6
3,test_compute函数开始执行,断言失败

当fixture标记的函数没有返回值时,执行用例,改fixture标记函数返回值默认为None

creat_id = None

def test_compute(creat_id):
> some=creat_id+4
E TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
test_study.py:41: TypeError

 

如果想要实现该creat_id函数能够被其他模块/类等共享使用,可以把写入到conftest.py文件,并写上scope对应的值,对于可能的值scope有:function,class,module,package或session。不填写默认function

二,多个fixture使用

 

1.如果用例需要用到多个fixture的返回数据,fixture也可以返回一个元祖,list或字典,然后从里面取出对应数据。

@pytest.fixture()
def creat_id():
return {'a':2*3,'b':2+3}

def test_compute(creat_id):
some=creat_id['a']+4
some2 = creat_id['b'] + 4
assert some==11
assert some2 == 11

 

执行用例,可以看到creat_id得到一个字典,然后再分别取值

creat_id = {'a': 6, 'b': 5}

def test_compute(creat_id):
some=creat_id['a']+4
some2 = creat_id['b'] + 4
> assert some==11
E assert 10 == 11
test_study.py:41: AssertionError

 

2.也可以分成多个fixture,然后在用例中传多个fixture参数

@pytest.fixture()
def creat_id():
return 2*3

@pytest.fixture()
def creat_id2():
return 2+3

def test_compute(creat_id,creat_id2):
some=creat_id+4
some2 = creat_id2 + 4
assert some==11
assert some2 == 11

 

执行用例,可以看到creat_id = 6, creat_id2 = 5

creat_id = 6, creat_id2 = 5

def test_compute(creat_id,creat_id2):
some=creat_id+4
some2 = creat_id2 + 4
> assert some==11
E assert 10 == 11
test_study.py:46: AssertionError

 

三,fixture互相调用

 

将定义为fixture的方法的返回值用在其他方法中

四,fixture的作用范围

 

fixture里面有个scope参数可以控制fixture的作用范围:

@pytest.fixture(scope='function')
1
session>module>class>function
-function:每一个函数或方法都会调用,默认情况下是function
-class:每一个类调用一次,一个类中可以有多个方法
-module:每一个.py文件调用一次,该文件内又有多个function和class
-session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module

注:
fixture为session级别是可以跨.py模块调用的,也就是当我们有多个.py文件的用例的时候,如果多个用例只需调用一次fixture,那就可以设置为scope=“session”,并且写到conftest.py文件里。
conftest.py文件名称时固定的,pytest会自动识别该文件。放到项目的根目录下就可以全局调用了,如果放到某个package下,那就在该ackage内有效

scope越大,实例化越早
1.当函数调用多个fixtures的时候,scope较大的(比如session)实例化早于scope较小的(比如function或者class)
2.同样scope的顺序则按照其在测试函数中定义的顺序及依赖关系来实例化

fixture源码详解
fixture(scope=‘function’,params=None,autouse=False,ids=None,name=None):
scope:有四个级别参数"function"(默认),“class”,“module”,“session”
params:一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。
autouse:如果True,则为所有测试激活fixture func可以看到它。如果为False则显示需要参考来激活fixture
ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成
name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽,解决这个问题的一种方法时将装饰函数命令"fixture_“然后使用”@pytest.fixture(name=’’)"。

五,调用fixture的四种方法

 

1.函数或类里面方法直接传fixture的函数参数名称@pytest.fixture()

 

@pytest.fixture()
def test1():
print('\n开始执行function')

def test_a(test1):
print('---用例a执行---')

#执行结果
开始执行function
---用例a执行---

 

2.使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例,该方式获取不到fixture标识函数的返回值

如:@pytest.mark.usefixtures('test1')

@pytest.fixture()
def test1():
print('\n开始执行function')

@pytest.mark.usefixtures('test1')
def test_a():
print('---用例a执行---')

#执行结果
开始执行function
---用例a执行---

 

usefixtures与传fixture区别
usefixture,无法获取到返回值。传fixture,可以获取到返回值。
所以当fixture需要用到return出来的参数时,只能使用传fixture方式,当不需要用到return出来的参数时,两种方式都可以。

 

3.叠加usefixtures

如果一个方法或者一个class用例想要同时调用多个fixture,可以使用
@pytest.mark.usefixtures()进行叠加。
注意叠加顺序,先执行的放底层,后执行的放上层。
如:先执行test2 再执行test1

@pytest.mark.usefixtures('test1')
@pytest.mark.usefixtures('test2')
def test_xx(): #测试用例
pass

 

4.autouse设置为True,自动调用fixture功能

设置scope为module级别,在当前.py用例模块只执行一次,autouse=True自动使用
设置scope为function级别,每个用例前都调用一次,自动使用
如:@pytest.fixture(scope="module", autouse=True)

六,fixture使用yield实现setup和teardown
可以通过使用yield语句代替return,fixture里面的teardown用yield来唤醒teardown的执行,yield语句之后的所有代码都将用作拆卸代码

@pytest.fixture(scope="module")
def open():
print("\n1,打开浏览器,并且打开百度首页") #实现setup
yield 10 #“yield 10”替代“return 10”,且实现teardown
print("\n2,执行teardown!")
print("3,最后关闭浏览器")

def test_compute2(open):
assert open==10

def test_compute3(open):
assert open==9

 

执行时加上“-s -q”命令,可以发现,scope="module"代表每个模块调用一次,调用时先执行open中的第一行打印代码,然后yield 10返回数值9给测试函数使用,当两个测试函数都执行完毕后(一个成功一个失败),再执行yield后面的两行打印代码

1,打开浏览器,并且打开百度首页
.F
2,执行teardown!
3,最后关闭浏览器

 

yield遇到异常:
1.如果其中一个用例出现异常,不影响yield后面的teardown执行,运行结果互不影响,并且在用例全部执行完之后,会呼唤teardown的内容
2.如果在setup就异常了,那么是不会去执行yield后面的teardown内容了

七,使用带有参数化fixture
与在@ pytest.mark.parametrize中使用的方式相同,用于在参数化灯具的值集中应用标记。在fixture中使用params参数
是一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。如:
@pytest.fixture(params=[0, 1)
然后被fixture标记的函数需要使用request中的request.param才能使用到params中的值
1,

@pytest.fixture(params=[3,4,5, pytest.param(2, marks=pytest.mark.skip)])
def creat_id(request):
print('打印param的值',request.param)
return request.param+3

def test_compute(creat_id):
assert creat_id==6

 

执行时加‘-v’命令,可以看到每次测试函数的情况,
运行此测试将跳过对creat_id函数中value 的调用“2”,最后test_compute一共执行了3次,分别使用了creat_id函数的三个值,每次得到一个参数值;跳过1条

test_study.py::test_compute[3] 打印param的值 3
PASSED
test_study.py::test_compute[4] 打印param的值 4
FAILED
test_study.py::test_compute[5] 打印param的值 5
FAILED
test_study.py::test_compute[2] SKIPPED

 

六,在各个级别上覆盖fixture

1.一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在该层级以及以下目录生效。
2.conftest在不同的层级间的作用域不一样,再执行某模块时,先执行这个模块对应的祖先目录是否有conftest文件,然后依次往下级目录查找

3.conftest是不能跨模块调用的
4.conftest.py与运行的用例要在同一个pakage下,并且有init.py文件
注意:
Pytest对于每个fixture只会缓存一个实例,这意味着如果使用参数化的fixture,pytest可能会比定义的作用域更多次的调用fixture函数(因为需要创建不同参数的fixture)

可以使用pytest --fixtures xx.py 来查看可用的fixture
在相对较大的测试套件中,很可能需要override一个global或root一个已locally 定义的夹具,以保持测试代码的可读性和可维护性。

1,在文件夹级别(通过conftest文件)重写fixtures方法
如在/test1/conftest.py和/test1/test2/conftest.py中都有一个被fixture标识的同名方法:
则/test1/路径下的测试模块使用conftest.py中的的fixture方法,
而/test1/test2/路径下的测试模块会使用fixture.py文件中重写的fixture方法

test1/
__init__.py
conftest.py
#test1/ conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
test_something.py
# test1/test_something.py
def test_username(username):
assert username == 'username'

test2/
__init__.py
conftest.py
# test1/test2/conftest.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
test_something.py
# test1/test2/test_something.py
def test_username(username):
assert username == 'overridden-username'

 

2,在测试模块级别重写fixtures方法
如在/test1/conftest.py都有一个被fixture标识的同名方法:username()
/test1路径下有一测试模块test_something.py,重写了fixtures方法1:username()
/test1路径下有一测试模块test_something_else.py,重写了fixtures方法2:username()
这两个文件分别使用自己的fixtures方法:username()

test1/
__init__.py
conftest.py
# test1/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'

test_something.py
# test1/test_something.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
def test_username(username):
assert username == 'overridden-username'

test_something_else.py
# test1/test_something_else.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-else-' + username
def test_username(username):
assert username == 'overridden-else-username'

 

总结:
fixtures方法的查找方法
先从根目录下的fixture.py文件中查找,
依次往次级目录的fixture.py文件中查找,
最后再查找当前测试模块py文件中是否有该fixtures方法,使用最近的一个fixtures方法
注:同一个py文件中可以有同名的fixtures方法,也是使用最近的一个