pytest 使用

test_basic.py

Pytest 完全实战手册

一、核心概念与基础

1、在pytest框架下运行测试用例,最基础的一共有三点。

    1. 导入pytest的包
    1. 写一个方法,或者类。后面运行的时候,相当于运行这个方法,或者类里的方法,无需单个调用。
    1. pycharm 里,python基础工具,配置成pytest
      在这里插入图片描述
      在这里插入图片描述
      标红的从左向右依次是:
  • 对号表示可以查看所有通过的测试用例;

  • 禁止号表示可以查看所有忽略的测试用例;

  • 按a~z的顺序排序;

  • 按执行的时间排序;

  • 展开所有结果 收起所有结果;

  • 向上箭头表示执行前一个失败的用例;

  • 向下箭头表示执行下一个失败的用例;

  • 导出测试结果;

  • 钟表表示导入执行过的测试结果可选;
    最后一个是工具选项。

在这里插入图片描述
从上到下依次是:

  • 绿色的三角形 为运行测试方法;
  • 重新运行失败的测试用例;
  • 切换自动测试;
  • 配置运行环境
  • 停止测试;
  • 默认布局;
  • 钉住窗口。

1.1 Pytest 特性

  • 零继承架构:测试类无需继承任何基类
  • 命名约定
    • 测试文件:test_*.py*_test.py
    • 测试类:Test* 开头(推荐但非强制)
    • 测试方法:test_* 开头
  • 智能测试发现:自动收集符合命名规则的测试
    • (1)从一个或多个目录开始查找,可以在命令行指定文件名或目录名。如果未指定,则使用当前目录。
    • (2)在该目录和所有子目录下递归查找测试模块。
    • (3)测试模块指文件名为test_.py或_test.py的文件。
    • (4)在测试模块中查找以test_开头的函数名。
    • (5)查找名字以Test开头的类,其中,首先筛选掉包含__init__函数的类,再查找以test_开头类中的方法。
  • 原生断言:使用 Python 内置 assert 语句

1.2 基本测试结构

# test_basic.py
# -*- coding: utf-8 -*-
import pytest# 函数式测试
def test_addition_01():print("运行函数:test_addition")# 类式测试
class TestMathOperations:def test_subtraction_02(self):print("运行函数:test_subtraction")assert 5 - 3 == 2def test_multiplication_03(self):print("运行函数:test_multiplication")assert 2 * 3 == 6

结果:
在这里插入图片描述

二、测试执行控制

2.1 命令行核心参数

参数作用示例
-v详细输出pytest -v
-s显示打印输出pytest -s
-k关键字过滤pytest -k "add"
-x遇错即停pytest -x
--maxfail=n最大失败数pytest --maxfail=3
-n并发执行pytest -n 4
-q静默模式pytest -q
--collect-only只收集不执行pytest --collect-only

2.2 精确执行控制

# 执行特定文件
pytest tests/calculator/test_basic.py# 执行特定类
pytest test_api.py::TestLogin# 执行特定方法
pytest test_db.py::TestUser::test_create_user# 组合定位
pytest tests/integration/test_payment.py::TestCreditCard::test_3ds_verification

2.3 通过代码执行

# run_tests.py
import pytestif __name__ == "__main__":pytest.main(["-v", "--maxfail=2","tests/security/","tests/api/test_login.py::TestOAuth"])

三、高级测试组织

fixture是pytest特有的功能,它用@pytest.fixture标识,定义在函数前面。在编写测试函数的时候,可以将此函数的名称作为传入参数,pytest会以依赖注入方式将该函数的返回值作为测试函数的传入参数。 即被这个@pytest.fixture 装饰的函数,相当于执行了一次。然后把这个函数返回的结果,返回给这个函数名。 所以这个函数名,可以直接作为一个值传给另一个函数了。

3.1 夹具系统 (Fixtures)

3.1.1 夹具系统 (Fixtures) 基本功能 @pytest.fixture()

# -*- coding: utf-8 -*-
import pytest@pytest.fixture()
def data():test_data = {'name': 'linda', 'age': 18}return test_datadef test_login(data):name = data['name']age = data['age']print("作者的名字叫:{},今年{}岁.".format(name,age))

在这里插入图片描述

所以用这个装饰后,有两个作用

  • 1、第一个就是不用运行,直接用函数名,替代运行函数的操作。

  • 2、第二个好处和第一个本质是一样的,因为传入函数名后,相当于直接执行了这个函数,所以,可以在这个函数里加上我们想要的前置操作。

  • 3、如果是想后置操的话呢?参考下面的内容。

  • 如果测试数据是从csv文件中读取的,可以建立一个csv 文件,然后用一个函数去读取整个文件的数据。

# userinfo.csv文件username,age
ming,19
qing,100
ting,55
# -*- coding: utf-8 -*-
import pytest
import csv@pytest.fixture()
def data():test_data = {'name': 'linda', 'age': 18}return test_data@pytest.fixture()
def read_data():with open('./userinfo.csv') as f:row = csv.reader(f, delimiter=',')next(row)  # 不读取行首,跳到下一行users = []for r in row:users.append(r)  # 读取的字段均为str类型。print("前置操作是***********************************")        return usersdef test_login(read_data):name = read_data[0][0]age = read_data[0][1]print("作者的名字叫:{},今年{}岁.".format(name, age))

在这里插入图片描述

3.1.2 夹具系统 (Fixtures) 返回时,使用yield 代替return
  • 上面使用的,装饰后,直接调用函数名可以直接调用方法,也相当于前置操作。 如国想加上后置操作,可以用 yield (生成器)代替return 。
# -*- coding: utf-8 -*-
import pytest
import csv@pytest.fixture()
def data():test_data = {'name': 'linda', 'age': 18}print("前置操作是***********************************")yield test_dataprint("后置操作是***********************************")def test_login_01(data):print("运行test_login_01函数-------------------")def test_login_02(data):print("运行test_login_02函数-------------------")

在这里插入图片描述

3.1.3 夹具系统fixpipe, 去装饰函数的时候,装饰的范围是什么。

在这里插入图片描述

通过从 fixture这个函数能看出:
fixture(scope=“function”,params=None,autouse=False,ids=None,name=None):
scope有5个级别参数function(默认)​、class、module、package和session。

package被认为是实验性的,不用关注。

  • function:每个被装饰的 函数或方法都会调用;
  • class: 在一个个类里面的,一个类可以有多种方法,如果给每个函数和方法都装饰上,这个只会装饰一次。
# -*- coding: utf-8 -*-
import pytest
import csv@pytest.fixture(scope='class')
def data():test_data = {'name': 'linda', 'age': 18}print("前置操作是***********************************")yield test_dataprint("后置操作是***********************************")class Test_login():def test_login_01(self, data):print("运行test_login_01函数-------------------")def test_login_02(self, data):print("运行test_login_02函数-------------------")def test_login_03(self, data):print("运行test_login_03函数-------------------")

在这里插入图片描述

  • module:同理每个.py文件调用一次,该文件内又有多个function和class;
  • Session:同理多个文件调用一次,可以跨.py文件调用,每个.py文件就是module。

fixture为session级别是可以跨.py模块调用的,也就是当有多个.py文件用例的时候,如果多个用例只需调用一次fixture,那就可以设置为scope=“session”。 这种我们会和conftest.py配合使用。

  • 既然已经是跨模块,需要在.py模块之上。因此采用一个单独的文件conftest.py(必须是这个文件名),文件名称是固定的,pytest会自动识别该文件。放到工程的根目录下就可以全局调用了,如果放到某个package包下,那就只在该package内有效。
  • (1) 在本目录下创建conftest.py文件(文件名必须是这个)​。
  • (2) 将登录模块带 @pytest.fixture写在conftest.py文件中。
    在执行过程中当读到test_login_01时,如果在本用例中没找到,则去本目录下conftest.py中查找。如果找到就执行,如果找不到就报错。同时其他工程师也可以在本目录中新建文件,并使用test_login_01函数。
# -*- coding: utf-8 -*-
import pytest# 定义一个参数化 fixture
@pytest.fixture(params=[1, 2, 3])
def number(request):"""返回不同的数字"""return request.param  # request.param 是当前参数值# 测试函数将运行三次(每个参数一次)
def test_square(number):assert number * number == number ** 2

在这里插入图片描述

关键概念

  1. params 参数

    • 接受一个可迭代对象(列表、元组等)
    • 每个元素代表一个测试参数
  2. request 内置 fixture

    • 提供测试上下文信息
    • request.param 访问当前参数值
  3. 测试执行次数

    • 使用参数化 fixture 的测试函数会运行多次
    • 次数 = 参数数量 × 测试函数数量
场景 1:不同用户角色的权限测试
@pytest.fixture(params=["admin", "user", "guest"])
def user_role(request):"""模拟不同权限的用户"""role = request.paramreturn User(role=role)def test_admin_access(user_role):# 管理员有所有权限if user_role.role == "admin":assert user_role.can_access("admin_panel")assert user_role.can_edit("all")# 普通用户有部分权限elif user_role.role == "user":assert user_role.can_access("dashboard")assert not user_role.can_access("admin_panel")# 访客只有查看权限else:assert not user_role.can_edit("any")assert user_role.can_view("public")
场景 2:不同数据库连接测试
# -*- coding: utf-8 -*-
import pytestdef MySQLConnection():print("--MySQLConnection---")return ("--MySQLConnection---")def PostgresConnection():print("--PostgresConnection---")return ("--PostgresConnection---")def SQLiteConnection():print('----SQLiteConnection----')return ('----SQLiteConnection----')@pytest.fixture(params=["mysql", "postgresql", "sqlite"])
def db_connection(request):"""创建不同数据库的连接"""db_type = request.paramif db_type == "mysql":return MySQLConnection()elif db_type == "postgresql":return PostgresConnection()else:return SQLiteConnection()def test_db_query(db_connection):# result = db_connection.execute("SELECT 1")print("*********IN  test_db_query:{}************".format(db_connection))

在这里插入图片描述

场景 3:多语言环境测试
@pytest.fixture(params=["en_US", "zh_CN", "ja_JP"])
def locale(request):"""设置不同语言环境"""lang = request.paramset_locale(lang)return langdef test_translation(locale):greeting = translate("hello", locale)if locale == "en_US":assert greeting == "Hello"elif locale == "zh_CN":assert greeting == "你好"elif locale == "ja_JP":assert greeting == "こんにちは"

3.2 使用夹具

class TestAdminPanel:def test_user_management(self, admin_user, database):assert admin_user.has_permission("manage_users")users = database.list_users()assert len(users) > 0

3.3 参数化测试

  • 参数化介绍常见使用场景:简单注册功能,也就是输入用户名、输入密码、单击注册,而测试数据会有很多个,可以通过测试用例设计技术组织出很多测试数据,例如用户名都是字母,密码也都是字母,或者都是数字,也可是它们的组合,或是边界值长度的测试数据等。这时可以通过参数化技术实现测试数据驱动执行每组测试用例。测试数据与测试用例是多对一的关系,所以完全可以把它们分开来看,把数据部分抽象成参数,通过对参数的赋值来驱动用例的执行。参数化传递是实现数据驱动的一种技术,可以实现测试数据与测试用例分离。
  • 各个方面的参数化如下:
    • ·测试用例的参数化:使用@pytest.mark.parametrize可以在测试用例、测试类甚至测试模块中标记多个参数或fixture的组合;
    • ·参数化的行为可以表现在不同的层级上;
    • ·多参数的参数化:一个以上参数与数据驱动结果;
    • ·fixture的参数化:相关知识可参考第3.8节;
    • ·自定义参数化:可以通过pytest_generate_tests这个钩子方法自定义参数化的方案;
    • ·使用第三方插件实现数据驱动DDT。
import pytest@pytest.mark.parametrize("username,password,expected", [("admin", "secret", True),("guest", "123456", False),("", "", False),("admin", "wrong", False)
])
def test_login(username, password, expected):result = login(username, password)assert result == expected
  • 对于复杂类型的测试数据通常加上id或name来表明数据的含义,并标记测试要点。测试数据除了字符串以外,还可以是表达式,以及元组、字典、类等类型。使用ids关键字参数,自定义测试ID。

二、为什么使用夹具

Pytest 夹具系统详解

什么是夹具系统?

夹具(Fixture)系统是 pytest 最强大的功能之一,它提供了一种可复用的机制来:

  1. 准备测试环境(如数据库连接、配置文件)
  2. 管理测试资源(如临时文件、网络连接)
  3. 执行清理操作(如关闭连接、删除临时数据)
  4. 共享测试数据(如预定义的用户对象)

为什么需要夹具?

考虑以下场景:

# 没有夹具的情况
def test_user_creation():db = connect_db()  # 每个测试都要创建连接user = db.create_user(name="Alice")assert user.id is not Nonedb.close()  # 每个测试都要关闭连接def test_user_deletion():db = connect_db()  # 重复代码user = db.create_user(name="Bob")db.delete_user(user.id)assert not db.user_exists(user.id)db.close()  # 重复代码

问题

  • 大量重复代码
  • 资源管理容易出错
  • 维护困难

夹具如何解决这些问题?

基本夹具示例

import pytest# 定义夹具
@pytest.fixture
def database():print("\n=== 建立数据库连接 ===")db = Database()yield db  # 将对象提供给测试用例print("\n=== 关闭数据库连接 ===")db.close()# 使用夹具
def test_user_creation(database):  # 通过参数注入夹具user = database.create_user(name="Alice")assert user.id is not None

执行流程

1. 测试开始前: 执行 yield 之前的代码 (建立连接)
2. 执行测试用例: 使用数据库对象
3. 测试结束后: 执行 yield 之后的代码 (关闭连接)

夹具的核心特性

1. 作用域控制

通过 scope 参数管理资源生命周期:

作用域描述使用场景
function每个测试函数执行一次默认值,适合轻量资源
class每个测试类执行一次类中多个测试共享资源
module每个模块执行一次模块级共享资源
package每个包执行一次跨模块共享资源
session整个测试会话一次全局资源(如登录会话)
@pytest.fixture(scope="module")
def shared_resource():print("\n初始化模块级资源")resource = Resource()yield resourceprint("\n清理模块级资源")

2. 夹具依赖

夹具可以依赖其他夹具:

@pytest.fixture
def admin_user(database):  # 依赖 database 夹具return database.create_user(role="admin")def test_admin_permissions(admin_user):assert admin_user.has_permission("admin_panel")

3. 自动使用夹具

无需显式声明即可自动应用:

@pytest.fixture(autouse=True)
def setup_logging():logging.basicConfig(level=logging.INFO)print("\n日志系统已初始化")# 所有测试都会自动应用此夹具
def test_example():assert True

4. 参数化夹具

动态生成不同测试场景:

@pytest.fixture(params=["admin", "editor", "viewer"])
def user_role(request):return request.param  # 访问参数值def test_role_permissions(user_role):assert get_permissions(user_role) != []

高级夹具用法

1. 工厂模式夹具

创建多个实例:

@pytest.fixture
def user_factory(database):def create_user(name, role="user"):return database.create_user(name=name, role=role)return create_user  # 返回工厂函数def test_user_roles(user_factory):admin = user_factory("Admin", role="admin")guest = user_factory("Guest")assert admin.is_admin()assert not guest.is_admin()

2. 动态资源管理

@pytest.fixture
def temp_config(tmp_path):# 使用内置的 tmp_path 夹具config_file = tmp_path / "config.ini"config_file.write_text("[DEFAULT]\nlang=en_US")return config_filedef test_config_loading(temp_config):config = load_config(temp_config)assert config["DEFAULT"]["lang"] == "en_US"

3. 夹具重写

在特定位置覆盖夹具:

# conftest.py (全局)
@pytest.fixture
def database():return RealDatabase()# test_dev.py (局部覆盖)
@pytest.fixture
def database():return MockDatabase()  # 使用模拟数据库def test_with_mock_db(database):assert isinstance(database, MockDatabase)

最佳实践

1. 合理组织夹具

使用 conftest.py 文件管理共享夹具:

project/
├── conftest.py         # 全局夹具
├── database/
│   ├── conftest.py     # 数据库相关夹具
│   └── test_queries.py
└── api/├── conftest.py     # API 测试夹具└── test_endpoints.py

2. 命名规范

  • 夹具函数:名词 (如 database, admin_user)
  • 测试函数:test_动词 (如 test_create_user)

3. 避免过度使用

  • 只在需要共享资源时使用夹具
  • 简单测试可直接在测试函数内准备数据

常用内置夹具

夹具名称用途
tmp_path创建临时目录(返回 Path 对象)
tmpdir创建临时目录(返回 py.path)
capsys捕获 stdout/stderr
caplog捕获日志输出
monkeypatch临时修改环境/对象
request访问测试上下文信息
def test_output_capture(capsys):print("Hello, pytest!")captured = capsys.readouterr()assert "pytest" in captured.out

总结:夹具系统的价值

  1. 资源管理:自动化管理测试资源的生命周期
  2. 代码复用:消除重复的初始化和清理代码
  3. 依赖注入:解耦测试逻辑与测试环境
  4. 可维护性:集中管理环境配置,一处修改全局生效
  5. 灵活性:支持多种作用域和组合方式

通过夹具系统,pytest 实现了测试环境的声明式管理,让开发者能专注于测试逻辑本身,而不是繁琐的环境准备工作。

三、参数话是怎么实现的

Pytest 参数化测试详解

参数化测试是 pytest 最强大的功能之一,它允许您使用不同的输入数据运行同一个测试逻辑,从而显著减少重复代码并提高测试覆盖率。下面通过多个实际示例详细说明参数化实现方式:

1. 基本参数化实现

使用 @pytest.mark.parametrize 装饰器

import pytest# 简单的加法测试参数化
@pytest.mark.parametrize("a, b, expected", [(1, 2, 3),      # 测试用例1(0, 0, 0),      # 测试用例2(-1, 1, 0),     # 测试用例3(10, -5, 5)     # 测试用例4
])
def test_addition(a, b, expected):"""测试加法函数"""assert a + b == expected

执行结果:

test_math.py::test_addition[1-2-3] PASSED
test_math.py::test_addition[0-0-0] PASSED
test_math.py::test_addition[-1-1-0] PASSED
test_math.py::test_addition[10--5-5] PASSED

2. 参数化实现原理

pytest 的参数化本质上是一个测试生成器

  1. 解析 parametrize 装饰器的参数
  2. 为每组参数创建独立的测试用例
  3. 每个用例拥有唯一的ID(可自定义)
  4. 执行时按顺序运行所有生成的测试

3. 高级参数化技巧

3.1 参数化类方法

class TestMathOperations:@pytest.mark.parametrize("x, y, expected", [(4, 2, 2),(9, 3, 3),(15, 5, 3)])def test_division(self, x, y, expected):assert x / y == expected

3.2 多参数组合(笛卡尔积)

@pytest.mark.parametrize("a", [1, 10, 100])
@pytest.mark.parametrize("b", [2, 20, 200])
def test_multiplication(a, b):"""测试所有组合:1×2, 1×20, 1×200, 10×2..."""assert a * b == a * b  # 实际项目中应有具体逻辑

3.3 参数化夹具(动态生成测试数据)

import pytest# 动态生成用户数据的夹具
@pytest.fixture(params=[("admin", "secret123"),("editor", "edit_pass"),("viewer", "view_only")
])
def user_credentials(request):return request.paramdef test_login(user_credentials):username, password = user_credentials# 执行登录逻辑assert login(username, password) is True

4. 参数化高级应用

4.1 自定义测试ID

@pytest.mark.parametrize("input, expected",[("3+5", 8),("2*4", 8),("6/2", 3),("10-3", 7)],ids=["加法测试","乘法测试","除法测试","减法测试"]
)
def test_eval(input, expected):assert eval(input) == expected

4.2 从外部文件加载参数

import json
import pytestdef load_test_data():with open("test_data.json") as f:return json.load(f)# 从JSON文件加载测试数据
@pytest.mark.parametrize("data", load_test_data())
def test_with_external_data(data):result = process_data(data["input"])assert result == data["expected"]

4.3 条件参数化

import sys
import pytest# 根据环境条件选择参数
params = []
if sys.platform == "win32":params.append(("Windows", "C:\\"))
else:params.append(("Linux", "/home"))@pytest.mark.parametrize("os_name, home_dir", params)
def test_platform_specific(os_name, home_dir):assert get_home_directory() == home_dir

5. 参数化最佳实践

5.1 保持参数化数据简洁

# 推荐:使用变量提高可读性
VALID_EMAILS = ["test@example.com","user.name@domain.co","name+tag@domain.org"
]INVALID_EMAILS = ["invalid","missing@domain","@domain.com"
]@pytest.mark.parametrize("email", VALID_EMAILS)
def test_valid_email(email):assert validate_email(email)@pytest.mark.parametrize("email", INVALID_EMAILS)
def test_invalid_email(email):assert not validate_email(email)

5.2 组合参数化与夹具

import pytest@pytest.fixture
def calculator():return Calculator()# 参数化与夹具结合
@pytest.mark.parametrize("a, b, expected", [(5, 3, 8),(10, -2, 8),(0, 0, 0)
])
def test_calculator_add(calculator, a, b, expected):assert calculator.add(a, b) == expected

5.3 处理异常的参数化

import pytest@pytest.mark.parametrize("a, b, exception", [(1, 0, ZeroDivisionError),("text", 2, TypeError),(None, 5, ValueError)
])
def test_division_errors(a, b, exception):with pytest.raises(exception):divide(a, b)

6. 参数化在实战中的应用

场景:测试用户权限系统

import pytest# 用户角色数据
ROLES = ["admin", "editor", "viewer", "guest"]# 权限矩阵
PERMISSIONS = {"create": [True, True, False, False],"delete": [True, False, False, False],"edit": [True, True, True, False],"view": [True, True, True, True]
}# 生成参数化数据
def generate_permission_tests():for role_idx, role in enumerate(ROLES):for permission, access_list in PERMISSIONS.items():expected = access_list[role_idx]yield role, permission, expected@pytest.mark.parametrize("role, permission, expected", list(generate_permission_tests()))
def test_permission_access(role, permission, expected):user = create_user(role=role)assert user.has_permission(permission) == expected

执行结果示例:

test_permissions.py::test_permission_access[admin-create-True] PASSED
test_permissions.py::test_permission_access[admin-delete-True] PASSED
test_permissions.py::test_permission_access[admin-edit-True] PASSED
test_permissions.py::test_permission_access[admin-view-True] PASSED
test_permissions.py::test_permission_access[editor-create-True] PASSED
test_permissions.py::test_permission_access[editor-delete-False] PASSED
...

总结:参数化测试的核心价值

  1. 减少重复代码:相同测试逻辑只需编写一次
  2. 提高覆盖率:轻松测试多种边界条件和输入组合
  3. 测试数据分离:将测试数据与测试逻辑解耦
  4. 清晰报告:每个参数组合作为独立测试显示
  5. 灵活扩展:轻松添加新测试用例

参数化测试是 pytest 区别于其他测试框架的核心优势之一,通过将测试数据从测试逻辑中分离出来,您可以创建更简洁、更强大且更易维护的测试套件。

四、怎么实现和代码分析的参数话

Pytest 参数化测试数据分离实践

将参数化测试数据与测试代码分离是提高测试可维护性的重要实践。下面详细介绍多种实现方式,并提供完整示例:

1. JSON 文件存储测试数据

数据文件:test_data/addition.json

[{"a": 1, "b": 2, "expected": 3},{"a": 0, "b": 0, "expected": 0},{"a": -1, "b": 1, "expected": 0},{"a": 10, "b": -5, "expected": 5},{"a": 100, "b": 200, "expected": 300}
]

测试代码:test_math.py

import json
import pytestdef load_json_data(file_name):"""从JSON文件加载测试数据"""with open(f"test_data/{file_name}", encoding="utf-8") as f:return json.load(f)# 使用JSON参数化
@pytest.mark.parametrize("data", load_json_data("addition.json"),ids=lambda d: f"{d['a']}+{d['b']}={d['expected']}"
)
def test_addition_json(data):assert data["a"] + data["b"] == data["expected"]

2. YAML 文件存储测试数据(需要安装PyYAML)

安装依赖:

pip install pyyaml

数据文件:test_data/login.yaml

- username: "admin"password: "secure_pass"expected: truescenario: "有效管理员账户"- username: "editor"password: "edit123"expected: truescenario: "有效编辑账户"- username: "invalid_user"password: "wrong_pass"expected: falsescenario: "无效凭据"- username: ""password: "empty_user"expected: falsescenario: "空用户名"- username: "admin"password: ""expected: falsescenario: "空密码"

测试代码:test_auth.py

import yaml
import pytestdef load_yaml_data(file_name):"""从YAML文件加载测试数据"""with open(f"test_data/{file_name}", encoding="utf-8") as f:return yaml.safe_load(f)# 使用YAML参数化
@pytest.mark.parametrize("test_data", load_yaml_data("login.yaml"),ids=lambda d: d["scenario"]
)
def test_login_yaml(test_data):result = login_user(test_data["username"], test_data["password"])assert result == test_data["expected"]

3. CSV 文件存储测试数据

数据文件:test_data/multiplication.csv

a,b,expected,description
2,3,6,正整数
0,5,0,零乘数
-4,3,-12,负数乘正数
-2,-3,6,负负得正
10,0.5,5,小数乘法

测试代码:test_math.py

import csv
import pytestdef load_csv_data(file_name):"""从CSV文件加载测试数据"""data = []with open(f"test_data/{file_name}", encoding="utf-8") as f:reader = csv.DictReader(f)for row in reader:# 转换数值类型row["a"] = float(row["a"]) if "." in row["a"] else int(row["a"])row["b"] = float(row["b"]) if "." in row["b"] else int(row["b"])row["expected"] = float(row["expected"]) if "." in row["expected"] else int(row["expected"])data.append(row)return data# 使用CSV参数化
@pytest.mark.parametrize("data",load_csv_data("multiplication.csv"),ids=lambda d: d["description"]
)
def test_multiplication_csv(data):assert data["a"] * data["b"] == data["expected"]

4. Python 模块存储测试数据

数据文件:test_data/user_data.py

# 用户创建测试数据
USER_CREATION_DATA = [{"name": "Alice Johnson","email": "alice@example.com","role": "admin","expected_status": 201},{"name": "Bob Smith","email": "bob.smith@test.org","role": "editor","expected_status": 201},{"name": "Invalid User","email": "not-an-email","role": "viewer","expected_status": 400,"expected_error": "Invalid email format"},{"name": "","email": "empty@name.com","role": "guest","expected_status": 400,"expected_error": "Name cannot be empty"}
]# 用户删除测试数据
USER_DELETION_DATA = [(1, 204),  # 有效用户ID(999, 404),  # 不存在的用户ID("invalid", 400)  # 无效ID格式
]

测试代码:test_user_api.py

import pytest
from test_data import user_data# 用户创建测试
@pytest.mark.parametrize("user_data",user_data.USER_CREATION_DATA,ids=lambda d: d["name"] or "Empty Name"
)
def test_create_user(user_data):response = create_user_api(name=user_data["name"],email=user_data["email"],role=user_data["role"])assert response.status_code == user_data["expected_status"]if user_data["expected_status"] != 201:error_data = response.json()assert user_data["expected_error"] in error_data["message"]# 用户删除测试
@pytest.mark.parametrize("user_id, expected_status",user_data.USER_DELETION_DATA,ids=lambda x: f"ID={x[0]}-Status={x[1]}"
)
def test_delete_user(user_id, expected_status):response = delete_user_api(user_id)assert response.status_code == expected_status

5. 数据库存储测试数据

测试代码:test_db_integration.py

import pytest
import sqlite3@pytest.fixture(scope="module")
def test_db():"""创建内存中的测试数据库"""conn = sqlite3.connect(":memory:")cursor = conn.cursor()# 创建测试表cursor.execute("""CREATE TABLE test_data (id INTEGER PRIMARY KEY,input_a INTEGER,input_b INTEGER,expected INTEGER,test_name TEXT)""")# 插入测试数据test_cases = [(1, 2, 3, "Positive numbers"),(0, 0, 0, "Zeros"),(-1, 1, 0, "Negative and positive"),(10, -5, 5, "Positive and negative")]cursor.executemany("INSERT INTO test_data (input_a, input_b, expected, test_name) VALUES (?, ?, ?, ?)",test_cases)conn.commit()yield connconn.close()def load_db_data(db_conn):"""从数据库加载测试数据"""cursor = db_conn.cursor()cursor.execute("SELECT input_a, input_b, expected, test_name FROM test_data")return cursor.fetchall()# 使用数据库参数化
@pytest.mark.parametrize("a, b, expected, test_name",load_db_data(test_db()),ids=lambda x: x[3]  # 使用test_name作为ID
)
def test_addition_db(a, b, expected, test_name):assert a + b == expected

6. 动态生成测试数据

测试代码:test_data_generators.py

import pytest
import randomdef generate_performance_test_data():"""生成性能测试数据"""for size in [10, 100, 1000, 10000]:# 生成测试数据集data = [random.randint(1, 1000) for _ in range(size)]# 预期结果:排序后的列表expected = sorted(data)yield pytest.param(data, expected, id=f"Size_{size}")# 动态参数化性能测试
@pytest.mark.parametrize("input_data, expected",generate_performance_test_data()
)
def test_sorting_performance(input_data, expected):result = sorted(input_data)  # 实际排序操作assert result == expected

7. 混合数据源参数化

测试代码:test_mixed_sources.py

import pytest
from test_data import user_data
from .data_loader import load_json_data, load_csv_data# 从多个来源组合测试数据
def combined_test_data():# 来源1:JSON文件for data in load_json_data("api_test_cases.json"):yield pytest.param(data, id=f"JSON-{data['case_id']}")# 来源2:Python模块for data in user_data.API_TEST_CASES:yield pytest.param(data, id=f"Module-{data['id']}")# 来源3:CSV文件for data in load_csv_data("additional_cases.csv"):yield pytest.param(data, id=f"CSV-{data['test_id']}")# 使用混合数据源
@pytest.mark.parametrize("test_case",combined_test_data()
)
def test_api_endpoint(test_case):response = call_api(endpoint=test_case["endpoint"],method=test_case["method"],data=test_case["payload"])assert response.status_code == test_case["expected_status"]if "expected_data" in test_case:assert response.json() == test_case["expected_data"]

最佳实践总结

  1. 选择合适的数据格式

    • 简单结构:JSON/YAML
    • 表格数据:CSV
    • 复杂逻辑:Python模块
    • 动态数据:数据库/生成器
  2. 数据文件组织

    project/
    ├── src/
    ├── tests/
    │   ├── test_modules/
    │   ├── test_data/          # 所有数据文件
    │   │   ├── json/
    │   │   ├── yaml/
    │   │   ├── csv/
    │   │   └── __init__.py     # Python数据模块
    │   └── conftest.py
    
  3. 数据加载器封装

    # tests/data_loader.py
    import json
    import csv
    import yaml
    import osDATA_DIR = os.path.join(os.path.dirname(__file__), "test_data")def load_json(relative_path):path = os.path.join(DATA_DIR, relative_path)with open(path, encoding="utf-8") as f:return json.load(f)def load_yaml(relative_path):path = os.path.join(DATA_DIR, relative_path)with open(path, encoding="utf-8") as f:return yaml.safe_load(f)# 其他加载函数...
    
  4. 数据验证

    • 使用JSON Schema验证数据结构
    • 添加数据版本控制
    • 编写数据完整性检查脚本
  5. 数据与测试分离优势

    • 非技术人员可维护测试数据
    • 数据变更不影响测试逻辑
    • 易于添加新的测试用例
    • 支持数据驱动的大规模测试
    • 提高测试代码可读性

通过以上方法,您可以实现测试数据与测试代码的清晰分离,创建更健壮、更易维护的测试套件,同时保持测试的灵活性和可扩展性。

四、 怎么写测试报错

Pytest 测试报告生成完全指南

测试报告是将测试结果可视化展示的关键工具,下面详细介绍多种生成专业测试报告的方法和工具:

一、内置报告输出

1. 控制台基础报告

# 详细模式(显示每个测试用例)
pytest -v# 显示打印输出
pytest -s# 组合使用
pytest -sv

2. 测试结果摘要

pytest -ra  # 显示所有结果摘要
pytest -rA  # 显示详细摘要(包括通过用例)

输出示例:

========================= short test summary info =========================
PASSED test_math.py::test_addition[1-2-3]
PASSED test_math.py::test_addition[0-0-0]
FAILED test_math.py::test_addition[-1-1-0] - assert (-1 + 1) == 0
PASSED test_auth.py::test_login[admin-secure_pass-True]

二、HTML 测试报告 (pytest-html)

1. 安装插件

pip install pytest-html

2. 生成基础报告

pytest --html=report.html

3. 高级配置

# 包含额外信息(环境、CSS等)
pytest --html=report.html --self-contained-html# 添加元数据
pytest --html=report.html \--metadata Python-version $(python --version) \--metadata Platform $(uname -a)

4. 自定义报告内容

conftest.py 中添加钩子:

def pytest_html_report_title(report):report.title = "自动化测试报告 - 2023"def pytest_html_results_summary(prefix, summary, postfix):prefix.extend([html.p("项目: 电商系统V2.0")])summary.extend([html.h2("关键指标统计")])def pytest_html_results_table_row(report, cells):if report.passed:cells.insert(1, html.td("✅"))elif report.failed:cells.insert(1, html.td("❌"))

三、Allure 高级报告

1. 安装与配置

# 安装Allure框架
# macOS: brew install allure
# Windows: scoop install allure# 安装pytest插件
pip install allure-pytest

2. 生成报告

# 运行测试并收集结果
pytest --alluredir=./allure-results# 生成HTML报告
allure generate ./allure-results -o ./allure-report --clean# 打开报告
allure open ./allure-report

3. 增强报告内容

在测试代码中添加丰富信息:

import allure
import pytest@allure.epic("认证模块")
@allure.feature("用户登录")
class TestLogin:@allure.story("成功登录场景")@allure.title("管理员登录测试")@allure.severity(allure.severity_level.CRITICAL)@pytest.mark.parametrize("username,password", [("admin", "secure_pass")])def test_admin_login(self, username, password):with allure.step("输入用户名和密码"):allure.attach(f"用户名: {username}", "输入信息")allure.attach(f"密码: {password}", "输入信息", allure.attachment_type.TEXT)with allure.step("点击登录按钮"):pass  # 模拟点击操作with allure.step("验证登录成功"):result = login(username, password)assert result is Trueallure.attach.file('./screenshots/login_success.png', name='登录成功截图', attachment_type=allure.attachment_type.PNG)@allure.story("失败登录场景")@allure.title("错误密码登录测试")def test_failed_login(self):with allure.step("输入错误密码"):result = login("admin", "wrong_pass")with allure.step("验证登录失败"):assert result is Falseallure.attach("""<h4>预期行为:</h4><ul><li>显示错误提示</li><li>登录按钮禁用3秒</li></ul>""", "验证点", allure.attachment_type.HTML)

四、JUnit XML 报告(持续集成集成)

1. 生成XML报告

pytest --junitxml=test-results.xml

2. XML报告示例

<?xml version="1.0" encoding="utf-8"?>
<testsuites><testsuite name="pytest" errors="0" failures="1" skipped="0" tests="4" time="0.5"><testcase classname="test_math" name="test_addition[1-2-3]" time="0.1"/><testcase classname="test_math" name="test_addition[0-0-0]" time="0.05"/><testcase classname="test_math" name="test_addition[-1-1-0]" time="0.1"><failure message="AssertionError: assert (-1 + 1) == 0">...</failure></testcase><testcase classname="test_auth" name="test_login" time="0.25"/></testsuite>
</testsuites>

3. 与CI工具集成

在Jenkins中使用JUnit插件:

pipeline {agent anystages {stage('Test') {steps {sh 'pytest --junitxml=test-results.xml'}}stage('Report') {steps {junit 'test-results.xml'}}}
}

五、自定义报告系统

1. 使用pytest钩子收集结果

# conftest.py
import json
from datetime import datetimedef pytest_sessionfinish(session, exitstatus):"""测试结束后生成自定义报告"""report_data = {"timestamp": datetime.now().isoformat(),"total": session.testscollected,"passed": session.testscollected - session.testsfailed - session.testsskipped,"failed": session.testsfailed,"skipped": session.testsskipped,"duration": round(session.duration, 2),"test_details": []}# 遍历所有测试项for item in session.items:report_data["test_details"].append({"nodeid": item.nodeid,"outcome": item.report.outcome if hasattr(item, 'report') else "unknown","duration": getattr(item.report, 'duration', 0),"error": getattr(item.report, 'longreprtext', '') if hasattr(item.report, 'longreprtext') else ''})# 保存为JSON文件with open("custom_report.json", "w") as f:json.dump(report_data, f, indent=2)

2. 生成可视化报告

使用Python创建HTML报告:

# generate_report.py
import json
from jinja2 import Template# 加载测试数据
with open('custom_report.json') as f:report_data = json.load(f)# HTML模板
html_template = """
<!DOCTYPE html>
<html>
<head><title>测试报告 - {{ timestamp }}</title><style>.passed { color: green; }.failed { color: red; }.skipped { color: orange; }table { width: 100%; border-collapse: collapse; }th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }tr:nth-child(even) { background-color: #f2f2f2; }</style>
</head>
<body><h1>自动化测试报告</h1><p>执行时间: {{ timestamp }}</p><div class="summary"><h2>测试概览</h2><p>总用例数: {{ total }}</p><p>通过: <span class="passed">{{ passed }}</span></p><p>失败: <span class="failed">{{ failed }}</span></p><p>跳过: <span class="skipped">{{ skipped }}</span></p><p>执行时长: {{ duration }} 秒</p></div><div class="details"><h2>测试详情</h2><table><tr><th>测试用例</th><th>状态</th><th>时长(秒)</th><th>错误信息</th></tr>{% for test in test_details %}<tr><td>{{ test.nodeid }}</td><td class="{{ test.outcome }}">{{ test.outcome }}</td><td>{{ test.duration | round(3) }}</td><td>{{ test.error | truncate(200) }}</td></tr>{% endfor %}</table></div>
</body>
</html>
"""# 渲染报告
template = Template(html_template)
html_report = template.render(**report_data)# 保存报告
with open("custom_report.html", "w") as f:f.write(html_report)print("自定义报告已生成: custom_report.html")

六、商业与开源报告工具对比

工具/插件类型特点适用场景
pytest-html开源简单易用,无需额外依赖快速生成基础报告
Allure开源高度可定制,支持步骤展示企业级测试报告
ReportPortal开源实时分析,AI辅助大型持续集成环境
Xray商业Jira集成,需求跟踪Jira用户
TestRail商业测试管理集成专业测试团队
Qase商业现代化界面,丰富APISaaS测试管理

七、最佳实践建议

  1. 分层报告策略

    • 开发环境:控制台报告(快速反馈)
    • CI流水线:JUnit XML + Allure(自动化分析)
    • 团队分享:HTML/PDF格式(易读易分享)
  2. 报告内容优化

    # 添加截图和日志
    @pytest.hookimpl(hookwrapper=True)
    def pytest_runtest_makereport(item, call):outcome = yieldreport = outcome.get_result()if report.when == "call" and report.failed:# 失败时截图driver = item.funcargs.get("browser")if driver:screenshot = driver.get_screenshot_as_base64()html = f'<div><img src="data:image/png;base64,{screenshot}"></div>'report.extra = [pytest_html.extras.html(html)]# 附加日志with open("test.log", "r") as log_file:report.extra.append(pytest_html.extras.text(log_file.read(), "日志"))
    
  3. 定期清理

    # 保留最近7天的报告
    find ./reports -name "*.html" -mtime +7 -exec rm {} \;
    
  4. 报告安全

    • 敏感信息脱敏处理
    • 访问权限控制
    • HTTPS传输报告
  5. 自动化归档

    # 示例:测试后自动归档报告
    pytest --html=reports/$(date +%Y%m%d).html
    

八、完整工作流示例

# 1. 运行测试并生成多种报告
pytest \--html=reports/$(date +%Y%m%d_%H%M).html \--junitxml=reports/junit-results.xml \--alluredir=reports/allure-results# 2. 生成Allure报告
allure generate reports/allure-results -o reports/allure-report --clean# 3. 归档报告
zip -r reports_$(date +%Y%m%d).zip reports/# 4. 发送通知
python send_notification.py --report reports/latest.html

通过合理选择报告工具和优化报告内容,您可以创建信息丰富、直观易懂的测试报告,有效提升团队对产品质量的可视性和信心。

六、 综述

Pytest 全功能测试用例实战教程

下面是一套完整的测试用例,涵盖了 pytest 的核心功能和高级特性。每个测试用例都包含详细注释,帮助您全面掌握 pytest 的使用方法。

"""
Pytest 全功能实战测试套件
包含 pytest 核心功能、高级特性和最佳实践
"""import pytest
import logging
import sys
import os
from datetime import datetime# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)# 示例业务逻辑函数
def add(a, b):"""加法函数"""return a + bdef divide(a, b):"""除法函数"""if b == 0:raise ValueError("除数不能为零")return a / bdef create_user(username, role="user"):"""创建用户"""if not username:raise ValueError("用户名不能为空")return {"username": username, "role": role, "created_at": datetime.now()}# ======================
# 1. 基础测试用例
# ======================
def test_addition_basic():"""基础测试示例"""result = add(2, 3)assert result == 5, "2+3应该等于5"class TestMathOperations:"""测试类示例"""def test_addition_in_class(self):"""类中的测试方法"""assert add(10, 20) == 30def test_negative_addition(self):"""负数加法测试"""assert add(-5, -3) == -8# ======================
# 2. 参数化测试
# ======================
@pytest.mark.parametrize("a, b, expected", [(1, 2, 3),      # 正整数(0, 0, 0),      # 零(-1, 1, 0),     # 负数与正数(2.5, 3.5, 6.0) # 浮点数
], ids=["正整数", "零", "负数与正数", "浮点数"])
def test_parametrized_addition(a, b, expected):"""参数化加法测试"""result = add(a, b)assert result == expected, f"{a}+{b}应该等于{expected},实际得到{result}"# ======================
# 3. 夹具系统 (Fixtures)
# ======================
@pytest.fixture(scope="module")
def database_connection():"""模块级夹具 - 模拟数据库连接"""logger.info("\n=== 建立数据库连接 ===")# 这里模拟数据库连接db = {"connected": True, "users": []}yield db  # 测试中使用这个对象logger.info("\n=== 关闭数据库连接 ===")# 清理操作db["connected"] = False@pytest.fixture
def admin_user(database_connection):"""函数级夹具 - 创建管理员用户"""user = create_user("admin_user", "admin")database_connection["users"].append(user)return user@pytest.fixture
def regular_user(database_connection):"""函数级夹具 - 创建普通用户"""user = create_user("regular_user")database_connection["users"].append(user)return userdef test_user_creation(admin_user, regular_user):"""测试用户创建"""assert admin_user["role"] == "admin"assert regular_user["role"] == "user"assert "created_at" in admin_userdef test_database_connection(database_connection):"""测试数据库连接"""assert database_connection["connected"] is True# ======================
# 4. 标记 (Marks) 和跳过
# ======================
@pytest.mark.slow
def test_slow_operation():"""标记为慢速测试"""# 模拟耗时操作import timetime.sleep(2)assert True@pytest.mark.skip(reason="功能尚未实现")
def test_unimplemented_feature():"""跳过未实现的功能测试"""assert False, "这个测试不应该执行"@pytest.mark.skipif(sys.version_info < (3, 8), reason="需要Python 3.8+")
def test_python38_feature():"""条件跳过测试"""# Python 3.8+ 的特性assert (x := 5) == 5  # 海象运算符@pytest.mark.xfail(reason="已知问题,待修复")
def test_known_bug():"""预期失败的测试"""assert add(0.1, 0.2) == 0.3  # 浮点数精度问题# ======================
# 5. 异常测试
# ======================
def test_divide_by_zero():"""测试除零异常"""with pytest.raises(ValueError) as exc_info:divide(10, 0)assert "除数不能为零" in str(exc_info.value)@pytest.mark.parametrize("username", ["", None, "   "])
def test_invalid_username(username):"""测试无效用户名"""with pytest.raises(ValueError) as exc_info:create_user(username)assert "用户名不能为空" in str(exc_info.value)# ======================
# 6. 临时文件和目录
# ======================
def test_file_operations(tmp_path):"""测试临时文件操作"""# 创建临时文件test_file = tmp_path / "test.txt"test_file.write_text("Hello pytest!")# 读取文件内容content = test_file.read_text()assert content == "Hello pytest!"# 创建子目录sub_dir = tmp_path / "subdir"sub_dir.mkdir()assert sub_dir.exists()# ======================
# 7. 输出捕获
# ======================
def test_output_capture(capsys):"""测试输出捕获"""print("标准输出消息")sys.stderr.write("标准错误消息")# 捕获输出captured = capsys.readouterr()assert "标准输出消息" in captured.outassert "标准错误消息" in captured.errdef test_log_capture(caplog):"""测试日志捕获"""caplog.set_level(logging.INFO)logger.info("这是一条信息日志")logger.warning("这是一条警告日志")assert "信息日志" in caplog.textassert "警告日志" in [rec.message for rec in caplog.records]# ======================
# 8. 并发测试 (需要pytest-xdist)
# ======================
@pytest.mark.parametrize("index", range(10))
def test_concurrent_execution(index):"""模拟并发测试"""import timetime.sleep(0.1)  # 模拟工作负载assert index < 10  # 总是成功# ======================
# 9. Allure 报告增强
# ======================
@pytest.mark.allure
class TestAllureFeatures:"""Allure 报告增强功能测试"""@pytest.mark.parametrize("a,b,expected", [(2, 3, 5), (5, 5, 10)])def test_parametrized_with_allure(self, a, b, expected):"""参数化测试与Allure结合"""result = add(a, b)assert result == expected# Allure 步骤if hasattr(pytest, 'allure'):import allurewith allure.step("验证加法结果"):allure.attach(f"{a} + {b} = {result}", "计算详情")def test_allure_attachments(self):"""测试Allure附件功能"""if hasattr(pytest, 'allure'):import allure# 添加文本附件allure.attach("这是一段文本附件", name="文本附件", attachment_type=allure.attachment_type.TEXT)# 添加HTML附件allure.attach("<h1>HTML附件</h1><p>这是一个HTML格式的附件</p>", name="HTML附件", attachment_type=allure.attachment_type.HTML)# ======================
# 10. 自定义标记和分组
# ======================
@pytest.mark.integration
def test_integration_feature():"""集成测试标记"""assert True@pytest.mark.security
class TestSecurityFeatures:"""安全测试分组"""def test_authentication(self):"""认证测试"""assert Truedef test_authorization(self):"""授权测试"""assert True# ======================
# 11. 夹具参数化
# ======================
@pytest.fixture(params=["admin", "editor", "viewer"])
def user_role(request):"""参数化夹具 - 不同用户角色"""return request.paramdef test_role_based_access(user_role):"""测试基于角色的访问控制"""if user_role == "admin":assert True  # 管理员有完全访问权限elif user_role == "editor":assert True  # 编辑者有部分权限else:assert True  # 查看者只有读取权限# ======================
# 12. 测试配置 (pytest.ini)
# ======================
# 实际项目中会在 pytest.ini 中配置
# 这里仅作演示
def test_config_usage():"""测试配置使用"""# 通常用于检查标记或配置选项assert "integration" in [mark.name for mark in test_integration_feature.pytestmark]# ======================
# 13. 测试选择和过滤
# ======================
# 这些测试用于演示选择功能,实际通过命令行执行
def test_select_by_keyword():"""可通过关键字选择的测试"""assert Truedef test_another_selectable_test():"""另一个可选择测试"""assert True# ======================
# 14. 自定义钩子和插件
# ======================
# 在 conftest.py 中实现
# 这里仅作演示
def test_custom_hook():"""测试自定义钩子(通常在conftest中实现)"""# 实际项目中可能有自定义行为assert True# ======================
# 15. 测试覆盖率 (需要pytest-cov)
# ======================
def test_coverage_important_function():"""重要功能测试(用于覆盖率)"""# 测试业务关键函数assert add(100, 200) == 300assert divide(10, 2) == 5.0# 测试边界情况with pytest.raises(ValueError):divide(5, 0)# ======================
# 16. 猴子补丁 (Monkeypatch)
# ======================
def test_monkeypatch_example(monkeypatch):"""使用猴子补丁修改环境"""# 修改环境变量monkeypatch.setenv("APP_ENV", "testing")assert os.getenv("APP_ENV") == "testing"# 修改系统路径monkeypatch.syspath_prepend("/custom/path")assert "/custom/path" in sys.path# 修改函数行为def mock_add(a, b):return 42monkeypatch.setattr("__main__.add", mock_add)assert add(2, 3) == 42# ======================
# 17. 测试执行顺序控制
# ======================
@pytest.mark.run(order=1)
def test_first():"""第一个执行的测试"""logger.info("首先执行")assert True@pytest.mark.run(order=3)
def test_third():"""第三个执行的测试"""logger.info("第三执行")assert True@pytest.mark.run(order=2)
def test_second():"""第二个执行的测试"""logger.info("第二执行")assert True# ======================
# 18. 测试依赖管理
# ======================
@pytest.mark.dependency()
def test_service_available():"""测试服务可用性"""# 模拟服务检查assert True@pytest.mark.dependency(depends=["test_service_available"])
def test_api_call():"""依赖服务的API调用测试"""# 只有服务可用时才执行assert True# ======================
# 19. 测试报告增强
# ======================
def test_report_enhancement():"""测试报告增强功能"""# 添加额外信息到报告pytest_html = pytest.config.pluginmanager.getplugin("html")if pytest_html:extra = pytest_html.extras# 添加文本extra.append(extra.text("额外的文本信息"))# 添加URLextra.append(extra.url("https://example.com"))# 添加图片# extra.append(extra.image("screenshot.png"))assert True# ======================
# 20. 工厂夹具模式
# ======================
@pytest.fixture
def user_factory():"""工厂夹具 - 创建用户"""def _create_user(username, role="user"):return create_user(username, role)return _create_userdef test_user_factory(user_factory):"""测试工厂夹具"""admin = user_factory("factory_admin", "admin")user = user_factory("factory_user")assert admin["role"] == "admin"assert user["role"] == "user"

如何运行这些测试

1. 基础运行

# 运行所有测试
pytest test_pytest_features.py# 显示详细信息
pytest -v test_pytest_features.py# 显示打印输出
pytest -s test_pytest_features.py

2. 选择性运行

# 运行特定标记的测试
pytest -m "slow or integration" test_pytest_features.py# 运行包含特定关键字的测试
pytest -k "addition" test_pytest_features.py# 运行特定类
pytest test_pytest_features.py::TestMathOperations# 运行特定方法
pytest test_pytest_features.py::test_parametrized_addition

3. 高级运行选项

# 并发运行(需要pytest-xdist)
pytest -n 4 test_pytest_features.py# 遇到第一个错误停止
pytest -x test_pytest_features.py# 允许最多2个失败
pytest --maxfail=2 test_pytest_features.py

4. 生成报告

# 生成HTML报告
pytest --html=report.html test_pytest_features.py# 生成Allure报告
pytest --alluredir=allure-results test_pytest_features.py
allure serve allure-results

学习要点总结

通过这套测试用例,您可以学习到:

  1. 基础测试结构:函数式测试和类测试
  2. 参数化测试:使用不同数据集运行相同测试逻辑
  3. 夹具系统:资源管理和依赖注入
  4. 标记系统:分组、跳过和预期失败测试
  5. 异常测试:验证代码是否按预期抛出异常
  6. 临时文件处理:使用 tmp_path 夹具
  7. 输出捕获:捕获 stdout/stderr 和日志
  8. 并发测试:使用 pytest-xdist 插件
  9. 报告增强:Allure 报告集成
  10. 猴子补丁:动态修改测试环境
  11. 测试顺序控制:管理测试执行顺序
  12. 测试依赖:指定测试之间的依赖关系
  13. 工厂夹具:创建灵活的对象实例
  14. 配置管理:使用 pytest.ini
  15. 覆盖率集成:使用 pytest-cov 插件

每个测试用例都包含详细注释,解释了其目的和使用的 pytest 功能。通过运行这些测试并查看结果,您将能够全面掌握 pytest 的核心功能和高级特性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/90708.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/90708.shtml
英文地址,请注明出处:http://en.pswp.cn/web/90708.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于OpenOCD 的 STM32CubeIDE 开发烧录调试环境搭建 DAPLINK/STLINK

需要部署一个开发环境,实现h7的板子通过daplink功能给目标板烧写程序(同事要将这个过程用fpga实现),需要通过openocd+gdb+daplink stm32; 总结:单条命令执行太麻烦,参考4写成脚本文件: 独立脚本使用Openocd ​ 在**“在Stm32CubeIDE环境下使用DAP-Link仿真”**一文中…

嵌入式硬件篇---zigbee无线串口通信问题

使用 ZigBee 进行无线串口通信时&#xff0c;接收异常&#xff08;如丢包、乱码、完全无法接收&#xff09;是常见问题&#xff0c;其原因涉及射频通信特性、网络机制、硬件配置、环境干扰等多个层面。以下从具体机制出发&#xff0c;详细分析可能的原因&#xff1a;一、射频层…

【AI周报】2025年7月26日

【AI周报】2025年7月第四周观察&#xff1a;GitHub Spark重塑开发范式&#xff0c;中美AI政策对垒升级 省流版静态页面周报&#xff0c;为方便各位看官快速食用&#xff0c;我准备了摘要版周报&#xff0c;欢迎访问&#xff1a;20250726周报 引言&#xff1a;本周焦点速览 2…

HTML:从 “小白” 到 “标签侠” 的修炼手册

目录 一、HTML&#xff1a;网页的 “骨架” 不是骷髅架 二、文本标签&#xff1a;文字的 “华丽变身” 术 1. 标题标签&#xff1a;文字界的 “领导班子” 2. 段落标签&#xff1a;文字的 “专属保姆” 3. 文本格式化标签&#xff1a;给文字 “穿花衣” 三、链接标签&…

python3GUI--基于YOLO的火焰与烟雾检测系统By:PyQt5(详细图文介绍)

文章目录一&#xff0e;前言1.引言2.正文二&#xff0e;核心内容1.数据集2.模型训练3.界面窗口1.登录注册界面2.核心功能界面3.检测告警提示窗口三&#xff0e;.核心界面模块介绍1.顶部信息区域2.数据输入3.参数配置4.告警设置5.操作台6.关于7.指标变化8.异常速览9.日志输出10.…

基于Transform、ARIMA、LSTM、Prophet的药品销量预测分析

文章目录有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主一、项目背景二、数据准备与预处理三、模型选择与方法设计1. ARIMA 模型&#xff1a;传统统计方法的基线构建2. LSTM 模型&#xff1a;引入记忆机制的深度学习方法3. Transformer 模型&#…

LLM隐藏层与logits数值的简单理解

LLM的“隐藏层数值”和“logits数值” 代表什么,范围是多少 “隐藏层数值”和“logits数值”是两个关键概念——它们分别对应模型“理解信息”和“输出决策”的核心环节。 一、先明确基础:LLM的“思考”流程 LLM本质是“输入文本→处理信息→输出结果”的神经网络。简单说…

Vue》》@ 用法

使用 别名导入 // 导入 src/components/Button.vue import Button from /components/Button.vue// 导入 src/utils/helper.js import { helperFunc } from /utils/helper// 导入 src/store/index.js import store from /store

20250726-1-Kubernetes 网络-Service存在的意义_笔记

一、Service控制器 1. Service存在的意义 1)基本场景  动态IP问题:Pod IP具有短暂性,销毁重建后IP会变化(示例:原IP 169.130重建后变为169.132) 服务发现需求:需要稳定入口访问同一服务的多个Pod,避免因Pod变动导致服务中断 负载均衡需求:多个Pod副本需要统一访…

在一个存在的包里面编写msg消息文件

前言尽管最佳实践是在专门的接口包中声明接口&#xff0c;但有时在同一个包中完成接口的声明、创建和使用会更为便捷。创建文件创建好msg/AddressBook.msg文件&#xff0c;在你的包的目录下package.xml<buildtool_depend>rosidl_default_generators</buildtool_depend…

华为服务器操作系统openEuler介绍与安装

一、openEuler概述 1.1、openEuler介绍 openEuler&#xff08;简称 “欧拉”&#xff09;是华为开源的服务器操作系统&#xff0c;是基于Linux稳定系统内核的、面向企业级的通用服务器架构平台。能够满足客户从传统IT基础设施到云计算服务的需求&#xff0c;打造了完善的从芯…

CUDA杂记--FP16与FP32用途

FP16&#xff08;半精度浮点数&#xff09;和FP32&#xff08;单精度浮点数&#xff09;是计算机中用于表示浮点数的两种常见格式&#xff0c;它们在存储空间、数值范围、精度、计算效率等方面有显著区别。以下从核心差异、适用场景等方面详细说明&#xff1a; 一、核心差异&am…

Android开发中技术选型的落地方案

技术选型不是简单地“哪个库最火就用哪个”&#xff0c;而是一个需要综合考虑业务、团队、技术、维护、未来等多维度因素的系统工程。 核心目标&#xff1a; 选择最适合当前及可预见未来项目需求的技术栈&#xff0c;确保应用高质量、高效率、可维护、可扩展、安全稳定地开发和…

Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成

在微服务架构盛行的今天&#xff0c;单元测试已成为保障代码质量的核心环节。Spring Boot 生态提供了完整的测试工具链&#xff0c;结合 JUnit5 的现代化测试框架和 Mockito 的行为模拟能力&#xff0c;可实现从方法级到模块级的全链路测试覆盖。本文将通过实战案例解析 JUnit5…

八股文整理——计算机网络

目录 OSI&#xff0c;TCP/IP&#xff0c;五层协议的体系结构 TCP/IP模型和OSI参考模型的对应关系 OSI每一层的作用如下&#xff08;理解顺序依次往下&#xff09;&#xff1a; OSI分层及对应协议 以 “寄快递” 为例类比七层模型 TCP与UDP的区别&#xff1f; TCP对应的…

进制间的映射关系

✅ 问题一&#xff1a;为什么不同进制之间会有特定的映射关系&#xff1f; ✅ 问题二&#xff1a;为什么八进制和十六进制可以被看作是二进制的简化形式&#xff1f;&#x1f50d; 一、为什么不同进制之间有特定的映射关系&#xff1f; 这是因为 所有进制本质上只是表示数的不同…

RabbitMQ-交换机(Exchange)

作者介绍&#xff1a;简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。中间件&#xff0c;我给它的定义就是为了实现某系业务功能依赖的软件&#xff0c;包括如下部分:Web服务器代理…

分类预测 | MATLAB实现DBO-SVM蜣螂算法优化支持向量机分类预测

分类预测 | MATLAB实现DBO-SVM蜣螂算法优化支持向量机分类预测 目录 分类预测 | MATLAB实现DBO-SVM蜣螂算法优化支持向量机分类预测 分类效果 基本介绍 算法步骤 参数设定 运行环境 应用场景 程序设计 参考资料 分类效果 基本介绍 该MATLAB代码实现了基于蜣螂优化算法(DBO)优…

变频器实习DAY15

目录变频器实习DAY15一、工作内容柔性平台常规测试柔性平台STO测试自己犯的一个特别离谱的错STO的功能了解为什么STO的故障叫做基极已封锁二、学习内容2.1 火线接断路器 vs. 接地/悬空的区别小内容分点附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)变频器实习DAY15 STO 板…

一文学会c++list

文章目录list简介list接口迭代器失效&#x1f6a9;模拟实现list简介 1&#xff0c;list是可以在常数时间复杂度任何位置随意插入的序列式容器&#xff0c;可以双向迭代 2&#xff0c;底层是双向链表结构&#xff0c;每个节点都是独立的&#xff0c;通过前后指针链接 3&#xf…