前言
前面一篇讲到用例加setup和teardown可以实现在测试用例之前或之后加入一些操作,但这种是整个脚本全局生效的,如果我想实现以下场景:
用例1需要先登录,用例2不需要登录,用例3需要先登录。很显然这就无法用setup和teardown来实现了。这就是本篇学习的目的,自定义测试用例的预置条件
Pytest提供了fixture机制,通过它可以在测试执行前后执行一些操作,类似setup和teardown。
目录
fixture优势
scope="function",作用范围是每个测试用例执行之前运行一次
当加上scope="class"时,当前模块下的所有类,都会调一次fixture,autouse=False时记得传参 。
多个测试用例调用一个fixture功能
#fixture为module级别时,在当前.py脚本里面所有用例开始前只执行一次
fixture参数详解
1.firture相对于setup和teardown来说应该有以下几点优势
例如很多时候,我们需要在测试用例执行前做数据库连接的准备,做测试数据的准备,测试执行后断开数据库连接,清理测试脏数据这些工作。
@pytest.fixture函数的scope可能的取值有function,class,module,package 或 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 items
test_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'也可以在函数上执行,如下图,传参给测试用例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 pytest
def 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也不会执行
#fixture为module级别时,在当前.py脚本里面所有用例开始前只执行一次
import pytest
@pytest.fixture(scope="module")
def fixture_module():
print("这是范围是module的fixture")
a='Tom'
return a
def test_1(fixture_module): #传参fixture_module
'''用例传fixture'''
print("测试账号:%s" % fixture_module)
assert fixture_module == "Tom"
class TestCase():
def test_2(self, fixture_module): #传参fixture_module
'''用例传fixture'''
print("测试账号:%s" % fixture_module)
assert fixture_module == "Tom"
if __name__ == "__main__":
pytest.main(["-vs", "test_fixturemodule.py"])
执行结果:
test_fixturemodule.py::test_1 这是范围是module的fixture
测试账号:Tom
PASSED
test_fixturemodule.py::TestCase::test_2 测试账号:Tom
PASSED
============================== 2 passed in 0.09s ==============================
Process finished with exit code 0
#fixture(scope="function", params=None, autouse=False, ids=None, name=None):
#使用装饰器@pytest.fixture()的name参数,指定测试固件(被装饰的函数)的新名字。
#通过装饰器@pytest.fixture()的参数params,实现测试固件的参数化。
#可以通过装饰器@pytest.fixture()的参数ids,设置测试用例的id。
#使用装饰器@pytest.fixture()的name参数,指定测试固件的名字。
#fixture(scope="function", params=None, autouse=False, ids=None, name=None):
import pytest
# 给装饰的测试函数重新命名为driver,如果不命名,默认login
@pytest.fixture(name = "driver")
def login():
print('登录系统')
token = 'a1b23c'
yield token
print('退出登录')
def test1(driver): #driver代替了login
print('in test1: ', driver)
print('测试1')
def test2(driver):
print('in test2: ', driver)
print('测试2')
#通过装饰器@pytest.fixture()的参数params,实现测试固件的参数化。
@pytest.fixture(params=['tom', 'jack'])
def login1(request):
print('%s登录' % request.param)
def test_1(login1):
print('执行测试1')
# 执行结果:
# setup_demo.py::test1[tom] tom登录
# 执行测试1
# PASSED
# setup_demo.py::test1[jack] jack登录
# 执行测试1
# PASSED
@pytest.fixture(params=[('tom', '123'), ('jack', '1234')])
def login2(request):
user = request.param[0]
passwd = request.param[1]
print('登录系统: 用户名%s, 密码%s' %(user, passwd))
def test_2(login2):
print('test 2')
# 执行结果:
# test_fixturename.py::test_2[login20] 登录系统: 用户名tom, 密码123 # 测试用例的id是login20
# test 2
# PASSED
# test_fixturename.py::test_2[login21] 登录系统: 用户名jack, 密码1234
# test 2
# 可以通过装饰器@pytest.fixture()的参数ids,设置测试用例的id。
@pytest.fixture(params=[('tom', '123'), ('jack', '1234')],
ids=['user_a', 'user_b']) # 这两个列表里,元素的数目要匹配
def login3(request):
user = request.param[0]
passwd = request.param[1]
print('登录系统: 用户名%s, 密码%s' %(user, passwd))
def test3(login3):
print('test 3')
#执行结果:
# test_fixturename.py::test3[user_a] 登录系统: 用户名tom, 密码123 #测试用例的id是user_a
# test 3
# PASSED
# test_fixturename.py::test3[user_b] 登录系统: 用户名jack, 密码1234
# test 3
# PASSED