pytest装饰器是在使用 pytest 测试框架时用于扩展测试功能的特殊注解或修饰符。使用装饰器可以为测试函数提供额外的功能或行为。
以下是 pytest 装饰器的一些常见用法和用途:
装饰器 | 作用 |
---|---|
@pytest.fixture | 用于定义测试用例的前置条件和后置操作。可以创建可重用的测试环境或共享资源,并将其注入到测试函数中。通常,fixture 可以返回所需的对象或执行特定的设置和清理操作。 |
@pytest.mark.parametrize | 用于参数化测试函数。通过在装饰的函数上提供参数列表,可以运行多组具有不同输入的测试用例。这样可以轻松地扩展测试范围并减少重复的测试代码。 |
@pytest.mark.skip | 使用这个装饰器可以跳过不需要运行的测试用例。可以附带参数来提供跳过测试的原因或条件。 |
@pytest.mark.skipif | 类似于 @pytest.mark.skip,这个装饰器可以基于条件来跳过特定的测试用例。可以使用预定义的环境变量、Python 版本、操作系统等作为条件。 |
@pytest.mark.xfail | 这个装饰器标记所装饰的测试用例为 “expected failure”(预期失败)。也就是说,预计在某些条件下测试将失败,如果出现预期的失败,将被视为测试通过;如果测试用例没有失败,则会被标记为测试失败。 |
@pytest.mark.repeat | 这个装饰器用于将测试用例重复运行多次。可以指定重复次数来确定运行次数。 |
@pytest.mark.usefixtures | 使用此装饰器可以在测试函数中直接使用已定义的 fixture,而无需在函数签名中显式声明。这样可以简化测试函数的编写。 |
@pytest.mark.dependency | 用于声明测试用例之间的依赖关系,以确保测试用例按正确的顺序执行。 |
常用的参数列表及其简要说明:
参数 | 说明 |
---|---|
scope | 指定fixture的作用域,控制fixture的生命周期。可选值包括"function"(默认值,每个测试函数调用一次),“class”(每个测试类调用一次),“module”(每个模块调用一次),或"session"(整个测试会话过程中调用一次) |
params | 为fixture指定不同的参数化值。可以是列表、元组或生成器。 |
autouse | 控制fixture是否自动应用于测试用例。如果将其设置为True,则fixture将自动应用于所有使用它的测试用例。 |
ids | 为参数化fixture中的每个参数指定一个名称或标识符列表,以便在测试报告中更好地识别不同的参数。 |
name | 为fixture指定一个显示名称,用于在测试报告中更好地标识fixture。 |
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# @pytest.fixture 声明这是一个夹具
# scope="function" 指定了夹具的作用域为函数级别,也就是每个测试函数都会调用该夹具
# autouse=True 指定夹具为自动使用的,也就是不需要在测试函数中显式地调用夹具,它会自动应用于每个测试函数
@pytest.fixture(scope="function", autouse=True)
def setup_browser():
"""
设置和关闭浏览器的测试夹具
"""
driver = webdriver.Chrome() # 初始化Chrome浏览器驱动
driver.get("https://www.baidu.com/") # 打开百度首页
yield driver # 返回driver对象供测试使用
driver.quit() # 测试结束后关闭浏览器
# @pytest.fixture 声明这是一个夹具
# params=[("csdn"......)] 设置夹具的参数列表,夹具被参数化为两个元组,每个元组包含两个值,表示不同的搜索引擎关键字和期望结果
# name="search_engine" 给夹具指定一个名字,这样在测试函数中可以引用该夹具
# ids=["CSDN", "Baidu Knowledge"] 为每个参数设置一个标识符,这些标识符在测试报告中将用于标识参数化的实例
@pytest.fixture(params=[("csdn", "CSDN - 专业开发者社区"), ("百度知识", "百度知道 - 全球领先中文互动问答平台")],
name="search_engine",
ids=["CSDN", "Baidu Knowledge"])
def parametrize_search_engine(request):
"""
参数化的搜索引擎测试夹具
"""
return request.param # 返回包含关键字和期望结果的元组
def test_search_home_page(setup_browser):
"""
测试在百度首页搜索功能
"""
assert "百度一下" in setup_browser.page_source # 检查页面源代码中是否包含"百度一下"
def test_navigation(setup_browser, search_engine):
"""
测试导航功能,包括输入关键字搜索并选择第一个结果
"""
driver = setup_browser # 获取浏览器驱动对象
keyword, engine = search_engine # 解包含有关键字和期望结果的元组
# 输入指定关键字,例如"csdn"或"百度知识"
driver.find_element(By.CSS_SELECTOR, '#kw').send_keys(keyword)
# 点击搜索按钮
driver.find_element(By.CSS_SELECTOR, '[class="bg s_btn"]').click()
time.sleep(5)
# 选择搜索结果中的第一个链接进行点击
driver.find_element(By.CSS_SELECTOR, '[class="result c-container xpath-log new-pmd"] h3 a').click()
time.sleep(5)
# 切换到最新窗口的句柄
driver.switch_to.window(driver.window_handles[-1])
assert driver.title == engine # 检查打开的页面标题是否与期望结果一致
def test_autouse_fixture():
"""
测试autouse=True是否生效
"""
assert 1 == 1
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
import pytest
# @pytest.mark.parametrize装饰器定义了一个参数化测试函数,允许我们一次运行多组数据进行测试
# 以下参数列表中的每个数据组都将分别传递给被装饰的测试函数
# 参数化允许我们在不同的输入值之间进行测试,以确保代码在各种情况下都能正常工作
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5), # 测试用例1:当a=2,b=3时,预期的输出结果应为5
(4, 5, 9), # 测试用例2:当a=4,b=5时,预期的输出结果应为9
(6, 7, 13) # 测试用例3:当a=6,b=7时,预期的输出结果应为13
])
def test_addition(a, b, expected):
# 断言语句用于检查表达式是否为真,以验证代码的正确性,
# 如果表达式为False,则会引发AssertionError异常。
assert a + b == expected # 检查a + b是否等于预期的输出结果
# pytest.main()函数被调用来运行测试
# 运行名为'test_run.py'的测试文件,并通过'-v'参数显示每个测试用例的执行结果
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
import pytest
# 该测试用例标记为 `@pytest.mark.skip` 装饰器,表示该测试尚未实现,应跳过执行
# 跳过的原因被指定为 "尚未实现",这通常用于临时禁用尚未准备好或需要进一步开发的测试
@pytest.mark.skip(reason="Not implemented yet")
def test_multiply():
assert 3 * 4 == 12
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
import pytest
import platform
# 使用 pytest.mark.skipif 装饰器为测试用例添加条件跳过的标记
@pytest.mark.skipif(platform.system() == "Windows"
and platform.release() == "10",
reason="跳过 Windows 10 版本的测试用例")
def test_linux_run():
# 仅在 Linux 系统下运行的测试代码
assert 2 == 2
@pytest.mark.skipif(platform.system() == "Linux",
reason="跳过在 Linux 系统下运行的测试用例")
def test_windows_run():
# 仅在 Windows 系统下运行的测试代码
assert 2 == 2
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
import pytest
# @pytest.mark.xfail 是Pytest测试框架中的一个装饰器,用于标记预期测试失败的测试用例
# 预期失败的测试用例,因为除以零会引发异常
@pytest.mark.xfail
def test_division():
assert 1 / 0 == 2
# 预期失败的测试用例,因为除以零不等于1,并且应该引发 ZeroDivisionError 异常
@pytest.mark.xfail(raises=ZeroDivisionError)
def test_division_zero():
assert 1 / 0 == 1
# 预期失败的测试用例,已知此加法操作会失败
@pytest.mark.xfail(reason="This test is known to fail")
def test_addition():
assert 2 + 2 == 5
# 预期失败的测试用例,这个测试实际上是正确的,但我们使用 xfail 标记将其标记为预期失败
@pytest.mark.xfail
def test_actually_correct():
assert 1 + 1 == 2
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
# pip install pytest-repeat 安装扩展插件
import pytest
# @pytest.mark.repeat 是 Pytest 测试框架中的一个装饰器,用于指定一个测试用例的重复运行次数
# 将该测试用例重复执行3次
@pytest.mark.repeat(3)
def test_addition():
result = 2 + 2
assert result == 4
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
import pytest
@pytest.fixture
def setup_data():
"""
前置条件 - 准备测试数据
"""
print("前置条件-->准备测试数据")
data = "test data"
yield data
print("后置条件-->清理测试数据")
def test_example(setup_data):
"""
示例测试方法,使用了前置条件 fixture 'setup_data'
"""
data = setup_data
print("执行示例测试")
assert data == "test data"
@pytest.mark.usefixtures("setup_data")
def test_method():
"""
方法测试,使用了前置条件 fixture 'setup_data',但不接收它作为参数
"""
print("执行方法测试")
assert 1 + 1 == 2
if __name__ == '__main__':
pytest.main(['test_run.py', '-s'])
# pip install pytest-dependency 安装扩展插件
import pytest
@pytest.mark.dependency()
def test_login():
# 登录测试
assert True
# 指定 test_login 为依赖
@pytest.mark.dependency(depends=["test_login"])
def test_search():
# 搜索测试
assert True
# 指定 test_login 和 test_search 为依赖
@pytest.mark.dependency(depends=["test_login", "test_search"])
def test_checkout():
# 结账测试
assert True
if __name__ == '__main__':
pytest.main(['test_run.py', '-v'])
import pytest
# 定义一个自定义装饰器,用于对测试用例进行分组
@pytest.mark.group1
def test_addition():
assert (1 + 2) == 3
# 定义第二个分组
@pytest.mark.group1
def test_subtraction():
assert (5 - 3) == 2
# 定义第三个分组
@pytest.mark.group2
def test_multiplication():
assert (2 * 3) == 6
# 定义第四个分组
@pytest.mark.group2
def test_division():
assert (10 / 2) == 5
# 如果当前文件被直接执行(而不是被导入为模块),则执行以下代码
if __name__ == '__main__':
# 运行 Pytest 并指定只运行属于 'group1' 分组的测试用例
pytest.main(['test_run.py', '-m', 'group1'])
[pytest]
markers =
group1: group1 测试的描述
group2: group2 测试的描述