目录
pytest_configure(config) 配置阶段调用
pytest_collection_modifyitems(config, items) 修改测试项
pytest_runtest_logstart(nodeid, location) 用例运行前调用
pytest_collection_finish(session) 测试结束后调用
1. 简介
- pytest是一个基于Python编写的轻量级测试框架,用于编写、组织和运行测试用例。它建立在Python标准库中的unittest模块之上,提供了更简洁、灵活和易于使用的测试功能。
pytest的作用
简化测试用例编写:pytest提供了简洁的语法和灵活的标记功能,使得编写测试用例更加简单和直观。它不仅支持传统的函数式测试用例,还支持类和方法级别的用例,以及参数化、夹具等高级特性。
自动查找测试用例:pytest能够自动发现项目中符合约定的测试用例。只需将测试文件命名为
test_*.py
或*_test.py
,pytest会自动查找并执行其中的测试函数。丰富的断言和校验:pytest内置了丰富的断言方法,用于比较和校验测试结果的正确性。这些断言方法简洁直观,可用于比较对象、序列、异常等各种情况。
夹具(Fixture)支持:夹具是pytest的一个核心概念,用于在测试用例执行前后进行一系列准备和清理工作。pytest提供了灵活且易用的夹具装饰器,能够为测试用例注入依赖、控制资源、模拟测试环境等。
参数化测试:pytest支持通过
@pytest.mark.parametrize
装饰器为测试用例传递不同的参数,实现参数化测试。这样可以避免编写大量类似的测试用例,提高测试覆盖率和代码的复用性。报告和测试结果可视化:pytest生成详细的测试报告,可以展示测试用例的执行结果和统计数据。同时,它还支持与其他工具集成,例如Jenkins、Travis CI等,方便实现持续集成和自动化测试。
pytest的注意事项
测试文件的命名规则:pytest默认会自动发现以"test_“开头或以”_test"结尾的测试文件,并执行其中的测试函数。因此,请确保测试文件符合这些命名规则,以便pytest能够正确发现和执行测试用例。
测试函数的命名规则:测试函数应以"test_"开头,以便pytest能够将其识别为测试用例。同时,命名应具有描述性,清晰地反映测试的目的和功能。
断言的使用:pytest提供了丰富的断言方法来比较和校验测试结果的正确性。对于每个测试,尽量使用恰当的断言来验证预期结果,并提供有意义的错误消息以便于排查问题。
公共文件 conftest.py 可以定义全局的测试配置。通过在测试目录的根目录或子目录中创建
conftest.py
文件,可以定义夹具、配置、插件、钩子函数等,以方便地管理和共享这些资源,提高测试的灵活性和可维护性。夹具的正确使用:夹具(Fixture)是pytest的重要特性,它可以在测试用例的准备和清理阶段执行代码。当使用夹具时,确保正确使用夹具装饰器
@pytest.fixture
,并在测试函数或类中以参数的形式使用夹具。参数化测试用例:使用
@pytest.mark.parametrize
装饰器可以实现参数化测试,减少冗余的测试代码。在进行参数化时,请确保谨慎选择参数组合,覆盖各种不同的测试情况。跳过测试用例:如果某个测试用例暂时无法通过或不需要执行,可以使用
@pytest.mark.skip
装饰器来跳过该测试用例。但请注意避免滥用跳过功能,以免隐藏潜在的问题。预期失败的用例:有时,某些测试用例提前知道会失败,可以使用
@pytest.mark.xfail
装饰器来标记这些预期失败的用例。这有助于记录已知的问题,并确保测试报告中正确显示预期失败的情况。代码覆盖率:可以使用pytest-cov插件来测量代码的覆盖率,以确保测试用例覆盖了足够的代码路径。这有助于发现可能存在的遗漏测试用例的情况。
使用命令行选项:pytest提供了许多命令行选项,用于控制测试的运行方式和执行参数。掌握这些选项将能够更好地使用pytest进行测试。
2. pytest 的基本用法
2.1. 编写测试函数
编写测试函数的步骤
命名测试函数:测试函数应以 "test_" 开头,pytest 可以自动识别并执行它们。
编写测试用例:在测试函数中编写用例来测试代码的各种情况。测试用例应包括输入数据、调用被测试代码的函数或方法,并对其返回结果进行断言。
使用断言进行验证:在测试用例中使用断言来验证预期结果。断言应该根据预期结果与实际结果的比较进行验证,并可以提供可读性强的错误消息。
示例代码(不需要调用函数,执行pytest自动调用)
def test_001_addition():
'''第一个测试用例,测试加法'''
assert 1 + 1 == 2 #断言
def test_002_subtraction():
'''第二个测试用例,测试减法'''
assert 2 - 1 == 2 #断言
def test_003_multiply():
'''第三个测试用例,测试乘法'''
result = 1 * 2
assert result == 3, f'预期为3, 实际结果为{result}' #断言并输出异常结果
def test_004_division():
'''第四个测试用例,测试除法'''
result = 1 / 1
assert result == 1, f'预期为1, 实际结果为{result}' #断言并输出异常结果,用例通过则不输出
def tmp_005_test():
'''非以test开头,不会自动执行该函数'''
assert 1 == 1
执行结果(pytest 文件)
结果信息可以分为4类
1、准备开始,统计用例数。
2、输出失败的函数,以E开头的行表示判断错误的行,并输出错误类型。
3、简要说明失败函数信息。格式为:文件名::函数名 - 异常信息。如果手动设置的失败时抛出的信息,则自动使用自定义的信息;若没有设置,则输出判断的行。
4、统计最终结果:失败数、成功数、耗时。
2.2. 编写测试类
- pytest 的测试类功能,可以组织和管理测试用例,提供更好的代码结构和可读性,共享设置和资源,并利用 pytest 的丰富功能来进行更灵活和精细的测试管理。这将显著提高测试代码的可维护性和可扩展性,以确保软件的质量和稳定性。
类的注意事项
命名规范:按照 pytest 的约定,测试类以 “Test” 开头,并且测试方法应该以 “test_” 开头。这样 pytest 可以自动识别并运行这些测试。
方法规定:pytest 不能收集带有
__init__()
构造函数的测试类,并且因此该测试类中的测试方法将不被执行。这是 pytest 的测试发现规则所致。独立性和隔离性:每个测试方法都应该是独立的,并且不依赖于其他测试方法的状态。确保每个测试方法都可以独立运行,并且不会受到其他测试方法运行的影响。使用 pytest 提供的夹具来共享和管理测试状态和资源,以确保测试方法的独立性和隔离性。
测试方法之间的顺序:请注意,pytest 不保证测试方法执行的顺序。确保测试方法之间没有依赖关系,并且可以独立执行。使用夹具的
autouse=True
参数可以帮助确保在每个测试方法运行之前和之后执行一些共享设置和清理操作。
示例代码(为2个类分别定义了2条用例)
class TestC01(object):
'''第一个测试类'''
def test_001(self):
assert 1 == 1
def test_002(self):
assert 2 == 2
class TestC02(object):
'''第二个测试类'''
def test_001(self):
assert 1 == 1
def test_002(self):
assert 2 == 3
执行结果
错误信息:文件名::类名::用例名 - 判断行
存在 init 函数的类不被执行
class TestC01(object):
'''存在init函数的类'''
def __init__(self):
self.v1 = 1
def test_001(self):
assert self.v1 == 1
使用类方法来替代 init 函数
class TestC01(object):
'''使用类方法来定义公共变量'''
@classmethod
def setup_class(cls):
cls.v1 = 1
def test_001(self):
'''判断公共变量是否正确'''
assert self.v1 == 1
2.3. 运行测试用例
直接模块中运行
def test_001():
'''测试用例1'''
assert 1 == 1
def test_002():
'''测试用例2'''
assert 2 == 2
if __name__ == '__main__':
pytest.main() # 使用pytest方法调用当前模块的全部函数
命令行运行
'''执行当前目录下及子目录下,所有以test开头的文件,执行类和函数'''
pytest
'''执行某个文件全部类'''
pytest test_module.py
'''指定执行某个文件中的类名'''
pytest test_module.py::TestClass
'''指定某个文件中的类名中的函数名'''
pytest test_module.py::TestClass::test_method
需要注意的是:pytest 在默认情况下只会查找以 “test_” 开头的文件、以 “test_” 开头的函数和以 “Test” 开头的类,但是不包含 __init__() 构造函数的类。
2.4. 断言
assert expression:判断表达式是否为真,如果为假,则抛出AssertionError异常。
assert expression, message:与上述断言相同,但可以自定义错误信息。
基本格式
assert 断言语句 #对某条语句断言
assert 断言语句,自定义信息 #若断言结果为失败,则输出自定义信息
相等性断言
assert <a> == <b> #验证 <a> 是否与 <b> 相等。
assert <a> != <b> #验证 <a> 是否与 <b> 不相等。
assert <a> is <b> #验证 <a> 是 <b> 的引用。
assert <a> is not <b> #验证 <a> 不是 <b> 的引用。
数值断言
assert <a> > <b> #验证 <a> 是否大于 <b>。
assert <a> < <b> #验证 <a> 是否小于 <b>。
assert <a> >= <b> #验证 <a> 是否大于等于 <b>。
assert <a> <= <b> #验证 <a> 是否小于等于 <b>。
成员关系断言
assert <a> in <b> #验证 <a> 存在于 <b> 中。
assert <a> not in <b> #验证 <a> 不在 <b> 中。
字符串断言
assert <string> == <expected> #验证 <string> 是否与 <expected> 完全相等。
assert <string>.startswith(abc) #验证 <string> 是否以 <abc> 开头。
assert <string>.endswith(abc) #验证 <string> 是否以 <abc> 结尾。
assert <substring> in <string> #验证 <substring> 是否存在于 <string> 中。
容器断言
assert <iterable> == <expected> #验证 <iterable> 是否与 <expected> 完全相等。
assert <iterable> < <expected> #验证 <iterable> 是否严格小于 <expected>。
assert <iterable> > <expected> #验证 <iterable> 是否严格大于 <expected>。
assert <iterable> <= <expected> #验证 <iterable> 是否小于等于 <expected>。
assert <iterable> >= <expected> #验证 <iterable> 是否大于等于 <expected>。
异常断言
with pytest.raises(<exception>) #验证是否抛出了指定的异常类型。
@pytest.mark.xfail #验证预期失败的测试。
2.5. 生成报告
生成 HTML 格式的报告
pytest --html=报告名.html
生成 JUnit XML 格式的报告
pytest --junitxml=报告名.xml
2.6. 发送邮件
提取html文件内容,通过outlook邮件发送给某个用户
代码示例
import smtplib
from email.mime.text import MIMEText
from email.utils import COMMASPACE
class TestC01(object):
'''准备测试用例1'''
@classmethod
def setup_class(cls):
cls.v1 = 1
def test_001(self):
assert self.v1 == 1
def test_002(self):
assert 2 == 2
class TestC02(object):
'''准备测试用例2'''
def test_001(self):
assert 1 == 1
def test_002(self):
assert 2 == 3
'''将执行结果输出到html文件,将html文件内容发送邮件'''
def send_mail():
'''定义一个发送邮件的方法'''
smtp_host = "smtp.office365.com" # SMTP 服务器地址
smtp_port = 587 # SMTP 服务器端口
username = 'xxx@outlook.com' # 发送账号
password = '123456' # 发送账号密码
recipients = ['x1@outlook.com', 'x2@outlook.com'] # 接收的账号
subject = 'pytest测试报告' # 邮件主题
html_content = open('report.html', 'r').read() # 读取html文件
# 构建邮件
msg = MIMEText(html_content, 'html', 'utf-8')
msg['Subject'] = subject
msg['From'] = username
msg['To'] = COMMASPACE.join(recipients)
# 发送邮件
with smtplib.SMTP(smtp_host, smtp_port) as smtp:
smtp.starttls() # 打开通信
smtp.login(username, password) # 登录账号,密码
smtp.send_message(msg) # 通过发件人、收件人发送邮件
send_mail()
执行结果(执行时需要指定输出为 html 文件)
邮件信息
也可以之间将html当做附件发送
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
class TestC01(object):
'''准备测试用例1'''
@classmethod
def setup_class(cls):
cls.v1 = 1
def test_001(self):
assert self.v1 == 1
def test_002(self):
assert 2 == 2
class TestC02(object):
'''准备测试用例2'''
def test_001(self):
assert 1 == 1
def test_002(self):
assert 2 == 3
'''将执行结果输出到html文件,将html文件内容发送邮件'''
def send_mail():
'''定义一个发送邮件的方法'''
smtp_host = "smtp.office365.com" # SMTP 服务器地址
smtp_port = 587 # SMTP 服务器端口
smtp_username = "xxx@outlook.com" # SMTP 邮箱用户名
smtp_password = "123456" # SMTP 邮箱密码
receiver_email = "xxx@outlook.com" # 收件人邮箱
subject = "pytest测试报告" # 邮件主题
report_file = 'report.html' # 文件名
# 构建邮件
msg = MIMEMultipart()
msg["From"] = smtp_username
msg["To"] = receiver_email
msg["Subject"] = subject
# 添加正文
body = MIMEText("请查收附件中的HTML报告")
msg.attach(body)
# 添加附件
with open(report_file, "r") as file:
attachment = MIMEApplication(file.read(), "html")
attachment.add_header("Content-Disposition", "attachment", filename=report_file)
msg.attach(attachment)
# 发送邮件
server = smtplib.SMTP(smtp_host, smtp_port) # 连接服务器
server.starttls() # 打开通信
server.login(smtp_username, smtp_password) # 登录账号、密码
server.send_message(msg) # 通过发件人、收件人发送邮件
server.quit() # 关闭服务器
send_mail()
执行结果(执行时需要指定输出为 html 文件)
邮件信息
3. 夹具(Fixture)的应用
- Fixture 是一个用来为测试函数提供可重复使用的设置和资源的机制。Fixture 提供了测试环境的初始化和清理等功能,可以帮助简化测试代码的编写,并提供了测试数据、配置和其他依赖项等。通过使用 Fixture,你可以在测试函数中使用提供的资源,而不需要在每个测试函数中重复编写相同的代码。
3.1. Fixture 的语法
- Fixture 在测试函数中通过装饰器
@pytest.fixture
定义,并作为函数参数注入到测试函数中。
'''常见定义方法'''
@pytest.fixture
def func():
pass
'''参数化'''
@pytest.fixture(params=[1, 2, 3])
def func(request): # request是固定的关键字
return request.param # .param也是固定的关键字
'''定义多个夹具'''
@pytest.fixture
def func1():
pass
@pytest.fixture
def func2():
pass
3.2. Fixture 的基本用法
基础示例
import pytest
@pytest.fixture
def set_data():
# 设置部分(准备数据、设置环境)
print('开始设置数据...')
data = [1, 2, 3]
# 使用yield将设置和清理两个部分分开
yield data
# 清理部分(释放资源、恢复环境、删除临时文件)
print('清理环境!')
def test_001(set_data):
'''定义一个测试用例,使用夹具中的set_data方法'''
print(set_data) # set_data中定义了一个变量data,所以它的值也是[1,2,3]
assert len(set_data) == 4 #断言
- 在 Fixture 方法中,通过在
yield
语句之前编写设置代码,并在yield
语句之后编写清理代码。yield
语句之前的代码段用于准备和设置测试环境,而yield
语句之后的代码段用于清理和恢复测试环境。 - 通过将设置部分和清理部分分开,Fixture 方法可以在测试函数执行前为其提供必要的设置,并在测试函数执行后进行相应的清理操作。这样可以确保测试函数在干净的环境中运行,并且资源得到适当的释放。
使用 Fixture 连接 MySQL
import pytest
import mysql.connector
'''定义一个连接数据库的夹具,作用域为当前模块'''
@pytest.fixture(scope="module")
def db_connection():
# 在设置阶段建立数据库连接
connection = mysql.connector.connect(
host="localhost",
user="username",
password="password",
database="database_name"
)
# 使用yield将设置和清理两个部分分开
yield connection
# 在清理阶段关闭数据库连接
connection.close()
'''定义一个需要连接数据库的用例'''
def test_query_data(db_connection):
cursor = db_connection.cursor() #建立连接
cursor.execute("SELECT * FROM my_table") #执行SQL
result = cursor.fetchall() #返回结果
assert len(result) > 0 #断言结果
cursor.close() #关闭连接
使用 Fixture 设置环境变量
import pytest
import os
'''定义一个设置环境变量的fixture,作用域为整个会话'''
@pytest.fixture(scope="session")
def set_env_variables():
# 在设置阶段设置环境变量
os.environ["API_KEY"] = "my_api_key"
os.environ["DB_HOST"] = "localhost"
# 使用yield将设置和清理两个部分分开
yield
# 在清理阶段删除环境变量
del os.environ["API_KEY"]
del os.environ["DB_HOST"]
'''定义一个简单的测试用例'''
def test_env_variables(set_env_variables):
# 在测试函数中访问设置的环境变量
api_key = os.getenv("API_KEY")
db_host = os.getenv("DB_HOST")
# 断言
assert api_key == "my_api_key"
assert db_host == "localhost"
使用 Fixture 登录接口
@pytest.fixture()
def login():
'''登录接口'''
def test_001(login): # 使用夹具方法
'''测试用例1,需要登录接口'''
def test_002(): # 不需要使用夹具方法
'''不需要登录接口'''
3.3. Fixture 的参数化(数据驱动)
① 参数与调用的关系
- 当参数个数为 0,所有用例调用 1 次
- 当参数个数为 n,所有用例调用 n 次
举例(设置3个参数,则每个用例调用3次)
import pytest
# Fixture 参数列表
@pytest.fixture(params=[1, 2, 3])
def set_data(request):
data = request.param
yield data
def test_001(set_data):
'''使用夹具的测试用例'''
assert set_data == 1
def test_002():
'''不使用夹具的测试用例'''
assert 1 == 1
Fixture 方法 set_data 中定义了3个参数,则使用 set_data 的用例将会执行3次(每调用1次,依次使用一个参数),而没有使用 set_data 的用例仅调用1次。
使用参数后,夹具方法的数据将不可被len使用
import pytest
# Fixture 参数列表
@pytest.fixture(params=[1])
def set_data(request):
data = request.param
yield data
def test_001(set_data):
print(type(set_data)) # 打印类型
assert len(set_data) == 1 # 断言len
② 参数化的使用
参数直接作用到装饰器
import pytest
'''直接将参数作用到装饰器'''
@pytest.fixture(params=[1, 2, 3])
def set_data(request):
data = request.param
yield data
def test_001(set_data):
'''使用夹具的测试用例'''
assert set_data == 1
使用多个 Fixture 参数
import pytest
'''定义第1个Fixture'''
@pytest.fixture(params=[1, 2])
def setup_data(request):
data = request.param
yield data
'''定义第2个Fixture'''
@pytest.fixture(params=[4, 5])
def setup_data2(request):
data = request.param
yield data
'''使用多个 Fixture 参数'''
def test_function(setup_data, setup_data2):
assert setup_data != setup_data2
执行次数将按笛卡尔积方法来确定 Fixture 的调用次数,执行流程如下:
- 1 对比 4
- 1 对比 5
- 2 对比 4
- 2 对比5
通过 @pytest.mark.parametrize 去组合多种参数
import pytest
'''定义第1个Fixture'''
@pytest.fixture()
def setup_data(request):
return request.param
'''定义第2个Fixture'''
@pytest.fixture()
def setup_data2(request):
return request.param
'''组合多个Fixture参数'''
@pytest.mark.parametrize("setup_data, setup_data2", [(1, 1), (2, 2), (3, 4), (4, 3)])
def test_001(setup_data, setup_data2):
assert setup_data == setup_data2
通过 @pytest.mark.parametrize 去指定 Fixture 方法名,并每次分别传入参数
通过一个变量去指定参数
import pytest
var = [1, 2, 3]
'''获取变量来定义Fixture参数'''
@pytest.fixture(params=var)
def set_data(request):
data = request.param
yield data
def test_001(set_data):
'''使用夹具的测试用例'''
assert set_data < 3
通过一个函数去指定参数
import pytest
def get_data():
return [1, 2, 3]
'''获取函数的参数来定义Fixture参数'''
@pytest.fixture(params=get_data())
def set_data(request):
data = request.param
yield data
def test_001(set_data):
'''使用夹具的测试用例'''
assert set_data < 3
③ 实例(接口测试)
import pytest
interface_address = ['接口地址1', '接口地址2', '接口地址3']
'''将接口地址按参数的方式驱动'''
@pytest.fixture(params=interface_address) # 指定参数
def set_data(request):
return request.param # 返回接口地址
'''每次根据一个地址,执行一次用例(按状态码判断)'''
def test_001(set_data):
result = 200 # 执行接口地址,返回状态码(举例返回的是200)
assert result == 200 # 断言状态码为200
3.4. Fixture 的作用域
设置作用域的语法
import pytest
@pytest.fixture(scope="类型")
1、函数级别的作用域(默认)
- 每个测试函数都会调用 Fixture 一次。当多个测试函数需要相同的 Fixture 实例时,可以使用函数级别作用域。
@pytest.fixture(scope="function")
def fixture():
pass
def test_func1(fixture):
# 测试函数 1
def test_func2(fixture):
# 测试函数 2
2、 类级别的作用域
- Fixture 实例对于类中的所有测试函数均为共享。
- Fixture 会在类的所有测试函数执行前创建,并在所有测试函数执行完后进行清理。
@pytest.fixture(scope="class")
def fixture():
pass
class TestClass:
def test_func1(self, fixture):
# 测试函数 1
def test_func2(self, fixture):
# 测试函数 2
3、模块级别的作用域
- Fixture 实例在整个模块中都为共享。
- Fixture 会在模块开始前创建,并在模块结束后进行清理。
@pytest.fixture(scope="module")
def fixture():
pass
def test_func1(fixture):
# 测试函数 1
def test_func2(fixture):
# 测试函数 2
4、会话级别的作用域
- Fixture 实例在整个测试会话期间都为共享。
- Fixture 会在测试会话开始前创建,并在测试会话结束后进行清理。
@pytest.fixture(scope="session")
def fixture():
pass
def test_func1(fixture):
# 测试函数 1
def test_func2(fixture):
# 测试函数 2
3.5. Fixture 的生命周期
Fixture 的生命周期分为3个阶段
1、设置(Setup)阶段:
- 在该阶段,Fixture 实例被创建并设置为可供测试函数使用的状态。
- 可以在 Fixture 方法中进行数据准备、资源初始化和环境设置等操作。
2、使用(Use)阶段:
- 在该阶段,在测试函数中使用 Fixture,获取 Fixture 方法返回的值。
- 测试函数通过在参数列表中包含 Fixture 的名称来调用 Fixture。
3、清理(Teardown)阶段:
- 在该阶段,对于具有范围(作用域)的 Fixture,执行清理操作以释放资源和还原环境。
- 清理操作可以包括关闭文件、断开连接、删除临时文件等等。
按作用域来区分生命周期
函数级别(function)作用域的 Fixture:
- 在每个测试函数之前创建 Fixture 实例。
- 在测试函数完成后清理 Fixture 实例。
- 每个测试函数都会获得 Fixture 实例的独立副本。
类级别(class)作用域的 Fixture:
- 在整个测试类开始前创建 Fixture 实例。
- 在所有测试函数完成后清理 Fixture 实例。
- 所有测试函数共享同一个 Fixture 实例。
模块级别(module)作用域的 Fixture:
- 在整个模块开始前创建 Fixture 实例。
- 在整个模块结束后清理 Fixture 实例。
- 整个模块中的所有测试函数共享同一个 Fixture 实例。
会话级别(session)作用域的 Fixture:
- 在整个测试会话开始前创建 Fixture 实例。
- 在整个测试会话结束后清理 Fixture 实例。
- 整个测试会话期间所有模块和测试函数共享同一个 Fixture 实例。
4. 高级用法
4.1. 跳过测试用例
使用 pytest.mark.skip
装饰器跳过用例
import pytest
@pytest.mark.skip(reason="跳过的原因,在报告中展示")
def test_001():
assert 1 == 1
def test_002():
assert 2 == 2
def test_003():
assert 3 == 3
使用 pytest.skip()
函数跳过测试用例
def test_001():
'''直接跳过'''
pytest.skip("跳过的原因")
assert 1 == 1
def test_002():
'''条件跳过'''
if 2 > 1:
pytest.skip("跳过的原因")
assert 2 == 2
def test_003():
assert 3 == 3
使用 pytest.mark.skipif
装饰器,根据条件跳过测试用例
import pytest
import sys
@pytest.mark.skipif(sys.platform == 'win32', reason='不适用于 Windows 平台')
def test_001():
assert 1 == 1
def test_002():
assert 2 == 2
4.2. 预期失败的用例
1、标记失败用例
直接将用例标记失败
@pytest.mark.xfail
def test_001():
assert False
通过条件判断将用例标记失败
@pytest.mark.xfail(sys.platform == 'win32', reason='不适用于 Windows 平台')
def test_002():
assert False
当引发特定异常时,标记用例失败
import pytest
def func():
raise ValueError
@pytest.mark.xfail(raises=ValueError)
def test_func():
func()
2、异常处理
自定义异常示例1
import pytest
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零") # 如果被除数为0,抛出自定义异常
return a / b
def test_divide():
with pytest.raises(ValueError) as excinfo: # 检查异常是否被引发
divide(10, 0) # 设置被除数为0
assert str(excinfo.value) == "除数不能为零" # 断言引发的异常结果,如果没有异常则用例失败
自定义异常示例2
import pytest
'''自定义异常类'''
class CustomException(Exception):
pass
'''调用自定义的异常类'''
def func():
raise CustomException("这是一个自定义异常")
'''判断func是自定义的异常'''
def test_exception_with_match():
with pytest.raises(CustomException, match="自定义"):
func()
4.3. 钩子函数
pytest_configure(config) 配置阶段调用
- 这个钩子函数在配置阶段调用,可以用来进行全局的初始化设置。可以在
conftest.py
文件中定义此钩子函数。 - 在
conftest.py
文件中定义的pytest_configure
钩子函数将会在 pytest 运行的配置阶段自动调用。这意味着每次运行 pytest 时,都会执行该钩子函数来进行初始化和配置。 conftest.py
文件是一个特殊的文件名,pytest 会自动识别它并加载其中的钩子函数和共享的夹具。所以确保将这个文件放置在你的项目中测试目录的根目录或者子目录中。
将方法写入公共 conftest.py 文件中
def pytest_configure(config):
# 添加自定义的配置项
config.addinivalue_line("markers", "slow: mark test as slow")
config.addinivalue_line("markers", "smoke: mark test as smoke test")
# 设置日志配置
setup_logging()
# 执行其他全局初始化操作
initialize_database()
def setup_logging():
# 设置日志级别和格式等
pass
def initialize_database():
# 初始化数据库连接等
pass
pytest_configure
钩子函数添加了两个自定义的标记slow
和smoke
,用于标记测试用例的类型。可以使用@pytest.mark.slow
或@pytest.mark.smoke
来选择性地运行测试。pytest_configure
钩子函数调用了setup_logging()
和initialize_database()
函数来设置日志配置和初始化数据库连接,以确保在运行测试之前进行了必要的全局初始化操作。
pytest_collection_modifyitems(config, items) 修改测试项
- pytest_collection_modifyitems(config, items): 这个钩子函数在收集测试用例之后进行调用,可以用来进行动态修改测试项目的列表。可以在 conftest.py 文件中定义此钩子函数。
- config:pytest 的配置对象,可以用于获取和修改框架配置。
- items:收集到的测试项列表,每一个测试项是一个 pytest.Item 对象,代表一个测试用例或测试集。
过滤测试项(根据某些条件过滤掉不需要执行的测试项)
def pytest_collection_modifyitems(config, items):
filtered_items = []
for item in items:
if not should_skip(item):
filtered_items.append(item)
items[:] = filtered_items
添加测试项(添加一些额外的测试项到收集的列表中)
def pytest_collection_modifyitems(config, items):
extra_item = create_extra_item()
items.append(extra_item)
修改测试项的排序(根据需求对测试项进行排序,例如按名称、标签、文件路径等进行排序)
def pytest_collection_modifyitems(config, items):
items.sort(key=lambda item: item.name)
pytest_runtest_logstart(nodeid, location) 用例运行前调用
- pytest_runtest_logstart 的作用是在测试项开始执行时记录相关的日志信息,例如测试项的名称、位置等。
- nodeid:测试项的标识符,通常是一个字符串,可以用来唯一标识测试项。
- location:测试项的位置信息,通常是一个元组 (filename, lineno, function),表示测试项所在的文件名、行号和函数名。
def pytest_runtest_logstart(nodeid, location):
# 获取测试项的文件名、行号和函数名
filename, lineno, function = location
# 打印测试项的开始执行事件
print(f"开始测试: {nodeid}")
print(f"定位信息: 文件名[{filename}], 行号[{lineno}], 函数名[{function}]")
pytest_collection_finish(session) 测试结束后调用
pytest_collection_finish
的作用是在测试集合完成后,对整个测试运行过程进行一些全局的定制操作,例如生成报告、统计测试项数量等。session 表示
当前的测试运行会话对象。
def pytest_collection_finish(session):
# 统计收集到的测试项数量
total_items = len(session.items)
# 输出测试项数量
print(f"测试用例总数为: {total_items}")
4.4. 异步多线程
@pytest.mark.asyncio
是 Pytest 框架中的装饰器,用于支持在测试中使用异步代码和协程。它可以与 Python 的内置异步库asyncio
结合使用,实现多线程执行异步测试用例。- 异步测试用例需要运行在支持异步编程的执行环境下,如 Python 3.7+。确保环境和依赖库能正确支持异步测试。
import pytest
import asyncio
'''异步用例'''
@pytest.mark.asyncio
async def test_001():
# 异步操作
await 异步操作
# 等待异步完成
await asyncio.sleep(1)
# 断言
assert 1 == 2
'''异步用例'''
@pytest.mark.asyncio
async def test_002():
# 异步操作
await 异步操作
# 等待异步完成
await asyncio.sleep(1)
# 断言
assert 2 == 2
'''非异步用例'''
def test_003():
assert 3 == 3
- 在测试用例中,可以使用
async
关键字定义异步函数,并使用await
关键字执行异步操作。 - 如果测试用例涉及到网络请求或其他I/O操作,可能需要使用适当的异步库(如
aiohttp
)或模拟库(如pytest-aiohttp
)来模拟异步行为。
异步等待时间注意点
- 根据测试用例的目标和预期结果,选择合适的等待时间。
- 考虑并发测试或性能测试时,设置适当的等待时间也很重要。比如:需要模拟更大的并发负载或更高的性能需求,适当缩短
asyncio.sleep
的等待时间,以增加测试的压力。- 设置过短或过长的
asyncio.sleep
时间都可能带来问题。太短的等待时间可能导致测试用例在异步操作完成之前就进行断言,从而导致测试结果不准确。而太长的等待时间可能会使测试用例的执行时间变长,降低整体测试效率。- 在实际场景中,可以根据具体的需求和测试结果进行多次尝试和调整来确定适合的
asyncio.sleep
时间。这样可以获得更准确和高效的异步测试用例。