【自动化测试基础】Pytest前后置处理
Pytest的前后置(固件、夹具)处理
有一些初始化配置和测试之后的收尾,只需要处理一次,这个时候我们就要用到夹具。
Pytest提供了以下几种setup和teardown方法:
setup_function和teardown_function: 用于每个测试函数setup_method和teardown_method: 用于每个测试方法(类级别)setup_module 和 teardown_module: 用于每个模块setup_class 和 teardown_class: 用于每个类setup和teardown: 用于每个测试用例(一般通过fixture实现)
"""
# 测试用例前后置固件setup和teardown使用
"""
def setup_module():
print("module前置")
def teardown_module():
print("module后置")
# def setup_function():
# print("函数前置")
# def teardown_function():
# print("函数后置")
#
# def test_03():
# print("测试333")
class TestSetupTeardown:
def setup(self):
print("所有用例前置,一般通过fixture实现")
def setup_class(self):
print("类前置")
def setup_method(self):
print("方法前置")
def test_01(self):
print("测试111")
def test_02(self):
print("测试222")
def teardown_method(self):
print("方法后置")
def teardown_class(self):
print("类后置")
def teardown(self):
print("所有用例前置,一般通过fixture实现")
if __name__ == '__main__':
pytest.main()
执行结果:

继承方式实现前后置
# setup_teardown.py文件
"""封装setup、teardown前置后置"""
class SetupTeardown:
def setup(self):
print("所有用例前置,一般通过fixture实现")
def setup_class(self):
print("类前置")
def setup_method(self):
print("方法前置")
def teardown_method(self):
print("方法后置")
def teardown_class(self):
print("类后置")
def teardown(self):
print("所有用例前置,一般通过fixture实现")
# 测试用例.py文件
"""类继承方式"""
from setup_teardown import SetupTeardown
class TestSetupTeardown(SetupTeardown):
def test_01(self):
print("测试111")
def test_02(self):
print("测试222")
if __name__ == '__main__':
pytest.main()
执行结果:

使用fixture装饰器来实现部分用例的前后置
import pytest
@pytest.fixture(scope="function")
def function_fixture():
print("函数级fixture setup")
yield
print("函数级fixture teardown")
@pytest.fixture(scope="class")
def class_fixture():
print("类级fixture setup")
yield
print("类级fixture teardown")
@pytest.fixture(scope="module")
def module_fixture():
print("模块级fixture setup")
yield
print("模块级fixture teardown")
@pytest.fixture(scope="session")
def session_fixture():
print("会话级fixture setup")
yield
print("会话级fixture teardown")
def test_one(function_fixture):
print("执行test_one")
assert True
class TestClass:
def test_two(self, class_fixture):
print("执行test_two")
assert True
使用pytest_generate_tests允许定义自定义参数化方案或扩展
通过数据驱动测试——Fixture参数化我们知道fixture可以使测试数据和测试用例分离,达到数据驱动测试的效果,
但是这样会有有个问题,测试数据集是直接写死在代码里的,
然而更多的时候测试数据的内容依赖于测试条件而变化,
所以固定的数据是无法满足我们的要求的。
好在pytest提供一个钩子函数pytest_generate_tests,它会在collection时执行,利用传入的参数metafunc为fixture赋值参数。
param metafunc共有五个属性值
metafunc.fixturenames:参数化收集时的参数名称metafunc.module:使用参数名称进行参数化的测试用例所在的模块d对象metafunc.config:测试用例会话metafunc.function:测试用例对象,即函数或方法对象metafunc.cls: 测试用例所属的类的类对象
根据metafunc.config.getoption("ip_type")获取到命令行参数值,并根据不同的输入利用metafunc.parametrize对test_data赋值参数,达到了动态参数化的功能。
# conftest.py
# 自定义参数
def pytest_addoption(parser):
parser.addoption("--ip_type", action="store", default="loopback",
help="ip type includes loopback, domain and local_network")
# pytest_generate_tests钩子函数
def pytest_generate_tests(metafunc):
"""
根据测试配置或定义测试函数的类或模块中指定的值生成测试用例, 在测试用例参数化收集前调用此钩子函数
:param metafunc:共有五个属性值
metafunc.fixturenames:参数化收集时的参数名称 --- test_开头的测试用例方法的形参列表,如:test_case(test_data)
metafunc.module:使用参数名称进行参数化的测试用例所在的模块d对象
metafunc.config:测试用例会话
metafunc.function:测试用例对象,即函数或方法对象
metafunc.cls: 测试用例所属的类的类对象
:return: none
"""
if 'test_data' in metafunc.fixturenames: # test_data --测试文件中测试方法的入参名;
ip_type = metafunc.config.getoption("ip_type")
if ip_type == "loopback":
metafunc.parametrize("test_data", ["127.0.0.1"]) # 构造对应测试方法的入参
elif ip_type == "local":
metafunc.parametrize("test_data", ["192.168.1.1", "192.168.1.2"])
# test_04.py 测试用例文件
def test_case(test_data):
print(test_data)
# 执行pytest
pytest.main(["-s", "--ip_type", "local", "testcase/test_04.py"])
执行结果:

高级用法
def pytest_generate_tests(metafunc):
"""
根据测试配置或定义测试函数的类或模块中指定的值生成测试用例, 在测试用例参数化收集前调用此钩子函数
:param metafunc:共有五个属性值
metafunc.fixturenames:参数化收集时的参数名称
metafunc.module:使用参数名称进行参数化的测试用例所在的模块d对象
metafunc.config:测试用例会话
metafunc.function:测试用例对象,即函数或方法对象
metafunc.cls: 测试用例所属的类的类对象
:return: none
"""
#print(metafunc.fixturenames)
# 测试用例中有casename、caserequest、caseexpect这三个fixture时,开始为测试用例设置参数化
if __GetRegionCoutry_testdata:
return
if all(list(filter(is_in_fixturename, metafunc.fixturenames))) and (list(filter(is_in_fixturename, metafunc.fixturenames))):
# 所有测试函数的参数化的测试数据(测试用例名字、测试用例的测试数据、测试用例的预期结果)
all_case_parametrize = []
case_parametrize = []
# 正在运行的测试函数的函数名或方法名
current_function_name = metafunc.function.__qualname__
# 正在检测的测试函数所在模块的名字
current_function_in_module_name = metafunc.module
# 从yaml中取出测试用例的测试数据
testdata_dict = __GetRegionCoutry_testdata
#print(testdata_dict.keys())
# 从模块中提取不需要参数化的测试函数名字
testcase = current_function_in_module_name.__dict__
del testcase[current_function_name]
if not testcase:
raise NameError("testcase字典为空")
not_parametrize_testcase = [key for key in testcase.keys() if "Rightrequest" in key]
# 不需要参数化的测试函数所需的测试数据
#__GetRegionCoutry_testdata = [dict((i, testdata_dict[i])) for i in not_parametrize_testcase if i in testdata_dict.keys()]
if not not_parametrize_testcase:
raise NameError("not_parametrize_testcase字典为空")
# print(not_parametrize_testcase)
for i in not_parametrize_testcase:
if i in testdata_dict.keys():
# 不需要参数化的测试数据
#print(i)
__GetRegionCoutry_testdata[i] = copy.deepcopy(testdata_dict[i])
# 需要参数化的测试数据
del testdata_dict[i]
#print(__GetRegionCoutry_testdata.keys())
#print("----------")
#print(testdata_dict.keys())
for key, value in testdata_dict.items():
case_parametrize.clear()
case_parametrize.append(key)
case_parametrize.append(value["request"])
case_parametrize.append(value["expect"])
all_case_parametrize.append(tuple(case_parametrize))
else:
if not all_case_parametrize:
raise NameError("all_casez_parametrize列表为空")
# 在测试用例运行前,对测试函数进行参数化设置
metafunc.parametrize("casename, caserequest, caseexpect", all_case_parametrize)
print("---")
# 垃圾回收
del testdata_dict
del testcase
del current_function_name
del not_parametrize_testcase
del case_parametrize
del all_case_parametrize
前置后置实际使用场景
- 数据库连接和关闭
- 文件打开和关闭
- 用例前置和后置步骤、数据处理
# test_database.py
import pytest
import sqlite3
@pytest.fixture(scope="module")
def db_connection():
print("创建数据库连接")
conn = sqlite3.connect(":memory:")
yield conn
print("关闭数据库连接")
conn.close()
@pytest.fixture(scope="function")
def db_cursor(db_connection):
cursor = db_connection.cursor()
print("创建表")
cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
yield cursor
print("删除表")
cursor.execute("DROP TABLE test")
def test_insert(db_cursor):
db_cursor.execute("INSERT INTO test (name) VALUES ('Alice')")
db_cursor.execute("SELECT * FROM test")
result = db_cursor.fetchall()
assert len(result) == 1
assert result[0][1] == 'Alice'
def test_delete(db_cursor):
db_cursor.execute("INSERT INTO test (name) VALUES ('Bob')")
db_cursor.execute("DELETE FROM test WHERE name='Bob'")
db_cursor.execute("SELECT * FROM test")
result = db_cursor.fetchall()
assert len(result) == 0
执行结果:

============================= 提升自己 ==========================
进群交流、获取更多干货, 请关注微信公众号:

> > > 咨询交流、进群,请加微信,备注来意:sanshu1318 (←点击获取二维码)
> > > 学习路线+测试实用干货精选汇总:
https://www.cnblogs.com/upstudy/p/15859768.html
> > > 【自动化测试实战】python+requests+Pytest+Excel+Allure,测试都在学的热门技术:
https://www.cnblogs.com/upstudy/p/15921045.html
> > > 【热门测试技术,建议收藏备用】项目实战、简历、笔试题、面试题、职业规划:
https://www.cnblogs.com/upstudy/p/15901367.html
> > > 声明:如有侵权,请联系删除。
============================= 升职加薪 ==========================
更多干货,正在挤时间不断更新中,敬请关注+期待。
进群交流、获取更多干货, 请关注微信公众号:

> > > 咨询交流、进群,请加微信,备注来意:sanshu1318 (←点击获取二维码)
> > > 学习路线+测试实用干货精选汇总:
https://www.cnblogs.com/upstudy/p/15859768.html
> > > 【自动化测试实战】python+requests+Pytest+Excel+Allure,测试都在学的热门技术:
https://www.cnblogs.com/upstudy/p/15921045.html
> > > 【热门测试技术,建议收藏备用】项目实战、简历、笔试题、面试题、职业规划:
https://www.cnblogs.com/upstudy/p/15901367.html
> > > 声明:如有侵权,请联系删除。
============================= 升职加薪 ==========================
更多干货,正在挤时间不断更新中,敬请关注+期待。
浙公网安备 33010602011771号