Pytest提供了fixture机制,通过它可以在测试执行前后执行一些操作,类似setup和teardown。
很多时候,我们需要在测试用例执行前做数据库连接的准备,做测试数据的准备,测试执行后断开数据库连接,清理测试脏数据这些工作。
@pytest.fixture函数的scope可能的取值有function,class,module,package 或 session。他们的具体含义如下:
- function,表示fixture函数在测试方法执行前和执行后执行一次。
- class,表示fixture函数在测试类执行前和执行后执行一次。
- module,表示fixture函数在测试脚本执行前和执行后执行一次。
- package,表示fixture函数在测试包(文件夹)中第一个测试用例执行前和最后一个测试用例执行后执行一次。
- session,表示所有测试的最开始和测试结束后执行一次。
通常,数据库连接和断开、测试配置文件的读取等工作,是需要放到session级别的fixture函数中,因为这些操作针对整个测试活动只需要做一次。而针对测试数据的读取,通常是function级别或者class级别的,因为测试数据针对不同的测试方法或者测试类往往都不相同。
@pytest.fixture装饰的函数的函数名可以作为测试方法的参数,在测试方法中,使用fixture函数名作为变量,就相当于是在调用fixture装饰的函数。
fixture(scope="function", params=None, autouse=False, ids=None, name=None):
# @pytest.fixture()如果不写参数,默认就是scope="function",它的作用范围是每个测试用例执行之前运行一次
# 如果autouse为True,则为所有测试直接激活fixture, 无需往每个函数传入fixture就可以调用它。 如果为False(默认值),则需要往测试函数传入fixture标记的函数名。
# fixture使用案例scope="function"
import pytest@pytest.fixture(autouse=False)
def fixture_for_func():print('这是fixture装饰器标记的函数')def test_1():print('执行了测试用例test_1')def test_2():print('执行了测试用例test_2')def test_3(fixture_for_func):print('执行了测试用例test_3')if __name__ == "__main__":pytest.main(["-s", "-v", "test_fixturedemo1.py"])
执行结果:
"C:\Program Files\Python37\python.exe" E:/PycharmProjects/api_pytest/tests/test_fixturedemo1.py
============================= test session starts =============================
platform win32 -- Python 3.7.1, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: E:\PycharmProjects\api_pytest, configfile: pytest.ini
plugins: allure-pytest-2.8.18
collected 3 itemstest_fixturedemo1.py 这是session的fixture
执行了测试用例test_1
.执行了测试用例test_2
.这是fixture装饰器标记的函数
执行了测试用例test_3
.============================== 3 passed in 0.12s ==============================Process finished with exit code 0
2、可以看出只有传入了函数名fixture_for_func的测试用例test_3执行测试用例前调用了一次fixture_for_func()函数
#如果autouse为True,则为所有测试直接激活fixture, 无需往每个函数传入fixture就可以调用它。
#fixture使用案例scope="function"
import pytest@pytest.fixture(autouse=True)
def fixture_for_func():print('这是fixture装饰器标记的函数')def test_1():print('执行了测试用例test_1')def test_2():print('执行了测试用例test_2')def test_3(fixture_for_func):print('执行了测试用例test_3')if __name__ == "__main__":pytest.main(["-s","-v","test_fixturedemo.py2"])
执行结果:
test_fixturedemo2.py::test_1 这是session的fixture
这是fixture装饰器标记的函数
执行了测试用例test_1
PASSED
test_fixturedemo2.py::test_2 这是fixture装饰器标记的函数
执行了测试用例test_2
PASSED
test_fixturedemo2.py::test_3 这是fixture装饰器标记的函数
执行了测试用例test_3
PASSED============================== 3 passed in 0.10s ==============================Process finished with exit code 0
当加上scope="class"时,当前模块下的所有类,都会调一次fixture,autouse=False时记得传参 。
#scope="class"范围的fixture'也可以在函数上执行,如下图,传参给测试用例test_2
#fixture使用案例scope="class"
import pytest@pytest.fixture(scope="class")
def fixture_for_class():print('用在测试类上的fixture')
#当加上scope="class"时,当前模块下的所有类,都会调一次fixture,autouse=False时记得传参
def test_1():print('执行了测试用例test_1')def test_2(fixture_for_class):print('执行了测试用例test_2')
#测试类上的fixture'也可以在函数上执行
def test_3():print('执行了测试用例test_3')class Test_Demo1():def test_4(self,fixture_for_class):print ("执行了测试test4")class Test_Demo2():def test_5(self):print ("执行了测试test5")if __name__ == "__main__":pytest.main(["-s","test_fixturedemo3.py"])
执行结果:
test_fixturedemo3.py 这是session的fixture
执行了测试用例test_1
.用在测试类上的fixture
执行了测试用例test_2
.执行了测试用例test_3
.用在测试类上的fixture
执行了测试test4
.执行了测试test5
.============================== 5 passed in 0.10s ==============================Process finished with exit code 0
# 1.上面的案例是在同一个.py文件中,多个测试用例调用一个fixture功能,如果有多个.py的文件都需要调用这个fixture功能的话,那就不能把fixture写到用例里面去了。
# 此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest提供了配置文件conftest.py。
# conftest.py配置文件需要注意以下点:
# conftest.py配置脚本名称是固定的,不能改名称
# conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件,conftest.py文件中的函数只在conftest.py所在目录及其子目录中的测试活动生效
# 不需要import导入 conftest.py,pytest用例执行时会自动查找
# @pytest.fixture(scope="session",autouse=True)
# def fixture_for_session():
# print('这是session的fixture')
#当@pytest.fixture函数范围是scope="session"时,不要像function,class,module一样,和编写的测试case放在一起,我们一般放在另一个文件conftest.py下
#conftest.py文件中的函数只在conftest.py所在目录及其子目录中的测试活动生效
#request.config.rootdir属性,这个属性表示的是pytest.ini这个配置文件所在的目录 #注意:当根目录下没有pytest.ini配置文件时,会默认指向conftest.py所在目录;此时要指向项目根目录,则在项目目录下新建一个 pytest.ini 空文件即可
# 此处定义的函数env用于提取数据,模块下的用例执行时,会自动读取conftest.py文件中的数据
import pytest
import yaml
import os
#定义一个fixture标记的函数env,scope="session" 表示这个fixture函数的作用域是session级别的,在整个测试活动中开始前执行,并且只会被执行一次
#conftest.py文件中的函数只在conftest.py所在目录及其子目录中的测试活动生效
@pytest.fixture(scope="session")
def env(request):config_path = os.path.join(request.config.rootdir, "config", "test", "config.yaml")#os.path.join(path1[, path2[, ...]]) 把目录和文件名合成一个路径,D:\python20190819\api_pytest\config\test\config.yaml#request.config.rootdir属性,这个属性表示的是pytest.ini这个配置文件所在的目录,D:\python20190819\api_pytest\#注意:当根目录下没有pytest.ini配置文件时,会默认指向conftest.py所在目录;此时要指向项目根目录,则在项目目录下新建一个 pytest.ini 空文件即可with open(config_path,encoding='utf-8') as f:env_config = yaml.load(f.read(), Loader=yaml.SafeLoader)#读取路径中的config.yaml文件中的数据return env_config@pytest.fixture(scope="session",autouse=True)
def fixture_for_session():print('这是session的fixture')
#当加上scope="session"时,不要像function,class,module一样,和编写的测试case放在一起,我们一般放在文件conftest.py下
#fixture使用案例scope="session"
import pytestdef test_s1(): #不传print("用例1")def test_s2(fixture_for_session): #session在整个测试活动中开始前执行,只会被执行一次,此处传参也不会调用print("用例2")def test_s3(fixture_for_session):print("用例3")if __name__ == "__main__":pytest.main(["-s","test_fixturedemo4.py"])
执行结果
test_fixturedemo4.py 这是session的fixture
用例1
.用例2
.用例3
.
通过测试结果可以看出,session在整个测试活动中开始前执行,只会被执行一次,即使传参给测试用例test_2也不会执行