前面介绍过pytest中的setup和teardown函数,包括模块级别、类、函数、方法级别的。但是有个缺陷,就是如果用例抛异常了,那么teardown函数就不会执行了。而fixture就不会这样,它不仅拥有setup和teardown的功能,而且在测试用例抛异常的时候,仍然能够继续执行。
下面来介绍fixture的功能。
其实,就是一个可以在事前和事后能够做一些准备工作和清理工作功能的一个装饰器。
setup和teardown有作用范围,那么fixture肯定不能输给它,fixture的作用范围是通过scope这个参数来决定的。scope的取值可以是以下任一一个,默认是function。
function:作用范围是:函数或者类中的方法。
class:作用范围是类,可以在类中的方法中调用。
module:作用范围是整个模块.py文件。
session:在多个.py文件中调用时,只会在第一个.py执行时执行事前的动作,在所有的.py执行完之后,执行事后的动作。
注意:上面的作用范围,对于class和module,被fixture装饰的函数的调用,是在class中第一个调用的地方才开始去执行被fixture装饰的函数,后面任何调用都不会再次去重新调用。module也是一样,session也是一样,在第一个文件执行的事时候执行事前的动作,在所有文件执行结束后,执行事后的动作。
要想让fixture满足事前(setup)和事后(teardown)的处理,那么必须得有yield,即被装饰的函数是一个生成器函数。事前处理yield之前的代码,包括yield返回值,事后处理yield之后的代码,其实就是两次的next(),如果没有yield,那么函数也没有指定return的话,函数默认返回的是none,下面给出一个用到了yield,因为一般都是用到yield的比较多。
fixture也是一个函数,查看源码有四五个参数:
fixture(scope=‘function’,params=None,autouse=False,ids=None,name=None):
一、scope=“function”(不填的话就是默认)
import pytest
@pytest.fixture()
def login1():
print("开始登录1....")
yield 1
print("退出登录1...")
def test_case4(login1):
print("我是一个外部函数测试用例....")
assert login1 == 1
class Test_case1(object):
def test_case1(self,login1):
print("测试用例1....")
assert 1 == 1
def test_case2(self):
print("测试用例2...")
# print(login)
assert 4 == 4
# print(login)
def test_case3(self,login1):
print("测试用例3....")
if __name__ == '__main__':
pytest.main('test_10_24.py')
它们是放在一个.py文件中,一般是放在不同的文件中,被fixture的函数是放在conftest.py文件中。pytest默认会去自己的当前文件中找有没有fixture函数,没有的话就会去conftest文件中找,找不到会去第三方库中找。
运行结果:
============================= test session starts =============================
collecting ... collected 4 items
test_10_24.py::test_case4 开始登录1....
PASSED [ 25%]我是一个外部函数测试用例....
退出登录1...
test_10_24.py::Test_case1::test_case1 开始登录1....
PASSED [ 50%]测试用例1....
退出登录1...
test_10_24.py::Test_case1::test_case2 PASSED [ 75%]测试用例2...
test_10_24.py::Test_case1::test_case3 开始登录1....
PASSED [100%]测试用例3....
退出登录1...
============================== 4 passed in 0.03s ==============================
可以看到只有在测试用例函数的参数列表中调用了,才会执行。且每个调用的函数都会在函数执行前做一个事前的工作,然后测试用例函数执行完之后,做一个事后处理的工作。
另外fixture函数调用后返回的值也可以被测试用例函数使用,用来进行断言。
二、scope=‘class’
放置在conftest.py文件中
import pytest
@pytest.fixture(scope='class')
def login1():
print("开始登录1....")
yield 1
print("退出登录1...")
放置在test_10_24.py文件中
import pytest
def test_case4(login1):
print("我是一个外部函数测试用例....")
assert login1 == 1
class Test_case1(object):
def test_case1(self,login1):
print("测试用例1....")
assert 1 == 1
def test_case2(self,login1):
print("测试用例2...")
# print(login)
assert 4 == 4
# print(login)
def test_case3(self):
print("测试用例3....")
if __name__ == '__main__':
pytest.main('test_10_24.py')
运行结果:
============================ test session starts =============================
collecting ... collected 4 items
test_10_24.py::test_case4 开始登录1....
PASSED [ 25%]我是一个外部函数测试用例....
退出登录1...
test_10_24.py::Test_case1::test_case1 开始登录1....
PASSED [ 50%]测试用例1....
test_10_24.py::Test_case1::test_case2 PASSED [ 75%]测试用例2...
test_10_24.py::Test_case1::test_case3 PASSED [100%]测试用例3....
退出登录1...
============================== 4 passed in 0.03s ==============================
可以看到类外面的函数,后被看作是类,在函数执行前会执行事前,函数结束会执行事后的动作。
类中被调用两次,但是并没有去调用两次,只是在第一次调用的时候执行了事前,后面不管是在类的什么位置被调用了,只会在类结束的时候执行事后的动作。
如果中间有调用,那么只会返回yield的值,不会进行事前和事后的处理。
三、scope=‘module’
import pytest
@pytest.fixture(scope='module')
def login1():
i = 1
print("开始登录1....")
yield i
i += 1
print("退出登录1...")
# @pytest.fixture()
# def login2(login1):
# print("登录2...")
# yield 2 + login1
# print("退出登录2")
test_10_24.py文件中代码保持不变。
运行结果:
=========================== test session starts =============================
collecting ... collected 4 items
test_10_24.py::test_case4 开始登录1....
PASSED [ 25%]我是一个外部函数测试用例....
test_10_24.py::Test_case1::test_case1 PASSED [ 50%]测试用例1....
test_10_24.py::Test_case1::test_case2 PASSED [ 75%]测试用例2...
1
test_10_24.py::Test_case1::test_case3 PASSED [100%]测试用例3....
退出登录1...
============================== 4 passed in 0.03s ==============================
可以看到事前和事后的动作只在module的第一次调用的位置执行事前,不管后面什么位置再次调用,也不会再次执行事前的动作,最后会在整个模块执行完之后执行事后的动作。
四、scope=‘session’
conftest.py文件中内容:
import pytest
@pytest.fixture(scope='session')
def login1():
i = 1
print("开始登录1....")
yield i
i += 1
print("退出登录1...")
# @pytest.fixture()
# def login2(login1):
# print("登录2...")
# yield 2 + login1
# print("退出登录2")
test_10_24.py文件中的内容:
import pytest
def test_case4(login1):
print("我是一个外部函数测试用例....")
assert login1 == 1
class Test_case1(object):
def test_case1(self,login1):
print("测试用例1....")
assert 1 == 1
if __name__ == '__main__':
pytest.main('test_10_24.py')
test_10_24_1.py文件中的内容:
import pytest
def test_case2( login1):
print("测试用例2...")
# print(login)
assert login1 == 1
print(login1)
if __name__ == '__main__':
pytest.main('test_10_24_1.py')
test_10_24_2.py文件中的内容:
import pytest
def test_case3():
print("测试用例3....")
if __name__ == '__main__':
pytest.main('test_10_24_2.py')
将上面的三个文件都执行了,如下:
在命令行中输入:
pytest -sv test_10_24.py test_10_24_1.py
test_10_24_2.py
执行结果:
collected 4 items
test_10_24.py::test_case4 开始登录1....
我是一个外部函数测试用例....
PASSED
test_10_24.py::Test_case1::test_case1 测试用例1....
PASSED
test_10_24_1.py::test_case2 测试用例2...
1
PASSED
test_10_24_2.py::test_case3 测试用例3....
PASSED退出登录1...
============================ 4 passed in 0.06s ============================
可以看到虽然第一个文件和第二个文件中调用了fixture函数,但是只是在第一个文件调用的时候执行了事前的动作,在最后一个文件执行结束后,执行了事后的动作。
上面的例子可以看到使用到fixture,就是在测试用例函数或者方法中直接将fixture函数的名称作为参数传入,其实还有一种方法,就是使用@pytest.mark.usefixtures(“fixture函数名”)
test_10_24.py文件的内容:
import pytest
# def test_case4(login1):
# print("我是一个外部函数测试用例....")
# assert login1 == 1
class Test_case1(object):
@pytest.mark.usefixtures('login2')
@pytest.mark.usefixtures('login1')
def test_case1(self):
print("测试用例1....")
assert 1 == 1
if __name__ == '__main__':
pytest.main('test_10_24.py')
可以看到上面的用例test_case1想要调用fixture的两个函数,使用了两次:
@pytest.mark.usefixtures(‘login2’)
@pytest.mark.usefixtures(‘login1’)
效果等同于:
def test_case1(self,login1,login2):
执行的顺序是从底往上。
但是@pytest.mark.usefixtures有一个缺点,就是fixture的返回值无法获取到。
还有一些扩展使用的方法:
第一、在conftest.py文件中可以定义多个函数,被fixture装饰器进行装饰,那么在别的.py文件中可以直接调用,且可以在一个方法或者函数的参数中,同时调用:
def test_case1(self,login1,login2):
print("测试用例1....")
assert 1 == 1
这里的login1和login2都是fixture函数。
第二、在conftest.py文件中的多个fixture函数还可以互相调用,但是要注意的是,比如fixture1调用fixture2时,那么fixture1的scope范围一定要比fixture2的范围小,否则就会报错,具体原因为什么会报错,我也没弄明白,可能是没有搞懂fixture装饰器的源码实现逻辑吧。后续搞懂了我会更新文章。。
import pytest
@pytest.fixture(scope='function')
def login1():
i = 1
print("开始登录1....")
yield i
i += 1
print("退出登录1...")
@pytest.fixture(scope='module')
def login2(login1):
print("登录2...")
yield 2 + login1
print("退出登录2")
test_10_24.py
import pytest
# def test_case4(login1):
# print("我是一个外部函数测试用例....")
# assert login1 == 1
class Test_case1(object):
def test_case1(self,login1,login2):
print("测试用例1....")
assert 1 == 1
if __name__ == '__main__':
pytest.main('test_10_24.py')
运行结果:
============================= test session starts =============================
collecting ... collected 1 item
test_10_24.py::Test_case1::test_case1 ERROR [100%]
test setup failed
ScopeMismatch: You tried to access the 'function' scoped fixture 'login1' with a 'module' scoped request object, involved factories
conftest.py:10: def login2(login1)
conftest.py:2: def login1()
============================== 1 error in 0.03s ===============================
如果想要所有的测试用例都执行,那么可以使用fixture中的参数autouse=True,默认是False,就是根据调用fixture函数来确定测试用例执不执行fixture,设置为True,表示所有的都执行。
conftest.py的作用域:
如果放在根目录下,那么作用域就是根目录下所有调用了它,以及子目录下调用了它的。如果放在某个文件夹下,那么作用域就是该文件夹以及其子目录。不能够跨文件夹使用,比如a和b是同级的文件夹,那么它们中的conftest都是独立的,不能够互相调用。