从入门到精通:用@ pytest.mark.parametrize实现数据驱动的完整路径

第一章:Pytest参数化测试的核心概念

在自动化测试中,参数化是提升测试覆盖率和代码复用性的关键技术。Pytest通过内置的 `@pytest.mark.parametrize` 装饰器,为开发者提供了简洁而强大的参数化支持,使同一测试函数能够使用多组不同输入数据执行多次验证。

参数化的基本语法

使用 `@pytest.mark.parametrize` 可以将测试数据与测试逻辑分离。装饰器接收两个主要参数:参数名字符串和参数值列表。

import pytest

@pytest.mark.parametrize("input_value, expected", [
    (2, 4),      # 输入2,期望输出4
    (3, 9),      # 输入3,期望输出9
    (4, 16),     # 输入4,期望输出16
])
def test_square(input_value, expected):
    assert input_value ** 2 == expected
上述代码中,测试函数 `test_square` 会被执行三次,每次传入不同的 `(input_value, expected)` 组合。Pytest会自动生成对应的测试用例ID,便于定位失败场景。

参数化的优势

  • 减少重复代码,提高测试脚本可维护性
  • 清晰分离测试数据与测试逻辑
  • 支持复杂数据结构,如字典、嵌套元组
  • 与fixture结合使用时,可实现更灵活的测试场景构建

多参数组合测试

当需要测试多个参数的组合时,可以直接扩展参数名定义:
input_xinput_yexpected_result
123
235
459

@pytest.mark.parametrize("input_x, input_y, expected_result", [
    (1, 2, 3),
    (2, 3, 5),
    (4, 5, 9)
])
def test_add(input_x, input_y, expected_result):
    assert input_x + input_y == expected_result

第二章:@pytest.mark.parametrize基础用法详解

2.1 理解数据驱动测试的设计理念

数据驱动测试(Data-Driven Testing, DDT)是一种将测试逻辑与测试数据分离的测试设计范式。其核心理念在于通过外部数据源驱动测试用例的执行,提升测试覆盖率并降低维护成本。
设计优势
  • 提高测试可维护性:修改数据无需更改测试代码
  • 增强复用性:同一测试逻辑可验证多组输入
  • 便于扩展:新增测试场景只需添加数据条目
典型实现示例

# 测试登录功能的多组凭证
test_data = [
    ("valid_user", "pass123", True),
    ("invalid_user", "wrong", False),
    ("", "pass123", False)
]

for username, password, expected in test_data:
    result = login(username, password)
    assert result == expected
上述代码中,test_data 包含用户名、密码和预期结果三元组,循环执行不同场景。结构清晰,易于向数据文件(如CSV、JSON)迁移。
数据源形式对比
数据源类型优点适用场景
内联列表简单直观少量测试数据
CSV/Excel易编辑共享多环境参数化
数据库高一致性复杂业务数据

2.2 单参数场景下的参数化实现

在自动化测试中,单参数场景是最基础的参数化用例。通过为测试方法传入不同数据值,可验证函数在多种输入下的行为一致性。
参数化基本结构
以 Python 的 `pytest` 框架为例,使用 `@pytest.mark.parametrize` 实现单参数参数化:
import pytest

@pytest.mark.parametrize("input_value", [1, -1, 0, 100])
def test_square(input_value):
    result = input_value ** 2
    assert result >= 0
上述代码中,input_value 是唯一参数,框架会依次将列表中的四个值注入测试方法,执行四次独立测试。每个值对应一个测试用例,提升覆盖率。
适用场景与优势
  • 适用于输入边界值、异常值、典型值的验证
  • 减少重复代码,提高测试可维护性
  • 清晰分离测试逻辑与测试数据

2.3 多参数组合的测试用例编写

在复杂系统中,函数或接口往往依赖多个输入参数,如何覆盖所有有效与边界组合成为测试关键。采用“笛卡尔积”方式生成全量组合虽全面,但成本过高,需引入等价类划分与正交法优化。
参数组合策略
  • 等价类划分:将每个参数的取值划分为有效与无效区间
  • 边界值分析:关注最小、最大及临界点输入
  • 正交实验法:利用正交表减少测试用例数量,保持高覆盖率
代码示例:多条件登录测试

# 参数:用户名状态、密码状态、验证码状态
test_cases = []
for username in ['valid', 'invalid']:
    for password in ['valid', 'empty']:
        for captcha in ['correct', 'expired']:
            test_cases.append((username, password, captcha))
上述代码生成 2×2×2=8 种组合,用于覆盖登录接口的核心路径。通过嵌套循环实现参数遍历,结构清晰,便于扩展。
组合优化对比
方法用例数覆盖率执行成本
全组合8100%
正交法4≈85%

2.4 使用argnames与argvalues自定义输入

在参数化测试中,argnamesargvalues 提供了灵活的输入定义方式,支持将测试用例数据结构化。
基本语法结构
import pytest

@pytest.mark.parametrize("x, y", [(1, 2), (3, 4), (5, 6)])
def test_add(x, y):
    assert x + y > 0
其中,argnames="x, y" 定义参数名,argvalues 提供对应值列表,每个元组映射一组输入。
复杂场景应用
使用字典提升可读性:
  • 通过 pytest.param 添加标签和条件
  • 支持跳过特定用例或标记预期失败
@pytest.mark.parametrize(
    "input,expected",
    [pytest.param(1, 2, id="basic"), pytest.param(3, 4, marks=pytest.mark.skip)]
)
def test_increment(input, expected):
    assert input + 1 == expected
该写法便于维护大量测试数据,并支持精细化控制执行行为。

2.5 参数化测试的执行流程与调试技巧

参数化测试通过将测试逻辑与多组输入数据解耦,提升用例复用性。其执行流程通常分为三个阶段:**数据准备、测试实例生成、逐组执行验证**。
执行流程解析
框架首先加载标注的参数源(如注解、方法引用),构建参数集;随后为每组参数创建独立测试实例;最后依次运行并记录结果。
常见调试策略
  • 启用日志输出,追踪每组参数的实际传入值
  • 结合断点调试,暂停在特定数据条目上分析状态
  • 使用命名策略(如 @DisplayName)标识参数组合,便于定位失败用例

@ParameterizedTest
@ValueSource(strings = { "apple", "banana" })
void shouldProcessFruit(String fruit) {
    assertNotNull(fruit);
    assertTrue(fruit.length() > 0);
}
上述代码中,@ValueSource 提供两组字符串输入,JUnit 将分别执行两次测试。调试时可通过 IDE 的“Run Parameterized Test”视图查看各轮次结果,精确识别哪一组参数引发异常。

第三章:进阶参数化技术实践

3.1 结合fixture实现动态数据注入

在自动化测试中,静态数据难以满足复杂场景需求。通过结合fixture机制,可实现运行时动态数据注入,提升测试灵活性。
动态数据注入原理
Fixture在测试前预加载数据,并支持依赖注入。利用其作用域和层级管理能力,可在不同测试阶段注入差异化数据。
代码示例

@pytest.fixture(scope="function")
def dynamic_user(request):
    user_id = request.param.get("id")
    return {"id": user_id, "name": f"user_{user_id}"}
上述代码定义了一个参数化fixture,request.param接收外部传入的参数,实现按需生成用户数据。
  • scope="function":限定fixture生命周期为函数级
  • request.param:获取传入的参数字典
  • 返回动态构造的用户对象

3.2 参数化中的条件跳过与标记控制

在参数化测试中,常需根据特定条件跳过某些用例或动态标记其行为。通过条件判断与元数据标注,可实现精细化的用例控制。
条件跳过的实现方式
使用 pytest.mark.skipif 可基于表达式决定是否跳过用例。例如:

import sys
import pytest

@pytest.mark.parametrize("value", [1, -1, 0])
def test_positive(value):
    if value <= 0:
        pytest.skip("仅支持正数")
    assert value > 0
该代码在运行时动态判断输入值,非正数则跳过后续断言,避免无效失败。
标记控制与分类管理
可通过标记对测试用例分类,结合参数化实现灵活执行策略:
  • @pytest.mark.smoke:标记为核心冒烟用例
  • @pytest.mark.skip(reason="临时禁用"):无条件跳过
  • @pytest.mark.xfail:预期失败,不计入错误
此类机制提升了参数化测试的可维护性与执行精度。

3.3 测试用例名称的自定义与可读性优化

良好的测试用例命名能显著提升代码可维护性与团队协作效率。清晰、语义化的名称使开发者快速理解测试意图,减少调试成本。
命名规范设计原则
遵循“行为-条件-预期”模式,确保名称具备自描述性:
  • 使用动词开头,明确操作行为
  • 包含输入条件与预期结果
  • 避免缩写和模糊词汇
代码示例:优化前后对比
// 优化前:含义模糊
func TestUser(t *testing.T) { ... }

// 优化后:语义清晰
func TestUserLogin_WithValidCredentials_ReturnsSuccess(t *testing.T) {
    // 测试逻辑
}
上述代码中,优化后的函数名明确表达了测试场景(有效凭证登录)与预期结果(成功返回),便于定位问题。
推荐命名模板
场景命名模板
正常流程MethodName_WithCondition_ReturnsExpected
异常处理MethodName_WithErrorInput_PanicsOrReturnsError

第四章:真实项目中的应用模式

4.1 对接外部数据源(JSON/CSV)进行参数化

在自动化测试中,对接外部数据源实现参数化是提升用例复用性和灵活性的关键手段。通过加载 JSON 或 CSV 文件,可将测试数据与脚本逻辑解耦。
JSON 数据源示例

[
  {
    "username": "user1",
    "password": "pass123",
    "expected": "success"
  },
  {
    "username": "guest",
    "password": "invalid",
    "expected": "failure"
  }
]
该 JSON 文件定义了两组登录测试数据。每个对象代表一组参数,在测试执行时逐条读取并注入用例。
CSV 数据读取流程
  • 使用文件流打开 CSV 资源
  • 按行解析字段,映射为测试变量
  • 结合数据驱动框架循环执行用例
结合测试框架如 TestNG 或 PyTest,可通过数据提供器机制实现自动迭代,显著提升测试覆盖率。

4.2 在API自动化测试中实现多场景验证

在API自动化测试中,单一用例难以覆盖服务的完整行为。通过构建多场景验证策略,可模拟正常、边界与异常流程,提升测试覆盖率。
测试场景分类
  • 正向场景:验证API在合法输入下的正确响应
  • 反向场景:测试非法参数、缺失字段等异常输入
  • 边界场景:验证极值输入,如空字符串、最大长度等
代码示例:使用Pytest实现多场景测试

@pytest.mark.parametrize("user_id,expected_status", [
    (1, 200),      # 正常用户
    (-1, 400),     # 无效ID
    (99999, 404),  # 用户不存在
])
def test_get_user(api_client, user_id, expected_status):
    response = api_client.get(f"/users/{user_id}")
    assert response.status_code == expected_status
该代码通过@pytest.mark.parametrize实现数据驱动,将不同输入与预期状态码组合执行,有效覆盖多个业务路径。参数化设计降低了重复代码量,提升维护效率。

4.3 与CI/CD集成提升回归测试效率

在现代软件交付流程中,将回归测试无缝集成至CI/CD流水线是保障代码质量的关键实践。通过自动化触发机制,每次代码提交均可自动执行测试套件,快速反馈问题。
自动化触发配置示例

jobs:
  regression-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Run regression tests
        run: |
          make test-regression
          ./scripts/upload-results.sh
上述GitHub Actions配置定义了回归测试任务:检出代码后执行预设的测试命令,并上传结果。其中make test-regression封装了测试环境准备、用例执行与报告生成逻辑。
集成带来的核心优势
  • 快速缺陷定位:变更引入的问题可在数分钟内被发现
  • 测试覆盖率持续可见:结合报告工具实现指标追踪
  • 发布节奏加速:减少手动验证环节,提升交付频率

4.4 性能边界测试中的参数化策略

在性能边界测试中,参数化策略能够系统化地探索系统极限。通过动态调整输入变量,可精准识别性能拐点。
参数化设计原则
  • 覆盖典型与极端场景,如高并发、大数据量
  • 确保参数正交性,避免测试用例冗余
  • 逐步递增压力梯度,便于定位瓶颈
代码示例:JMeter 参数化配置
<CSVDataSet config_language="en">
  <filename>test_data.csv</filename>
  <variableNames>users,rampUp,loopCount</variableNames>
</CSVDataSet>
该配置从 CSV 文件加载用户数、加压时间和循环次数,实现多维度压力组合测试。变量注入线程组后,可驱动不同负载模型。
参数组合影响分析
用户数响应时间(ms)吞吐量(req/s)
100120850
500480920
10001100950
数据显示,随着虚拟用户增加,系统吞吐量趋近上限,响应延迟显著上升,揭示了服务处理能力的边界。

第五章:从入门到精通的路径总结

构建系统化的学习路线
掌握一项技术不能依赖碎片化知识,必须建立清晰的学习路径。初学者应从基础语法和开发环境搭建入手,逐步过渡到模块化开发与工程实践。例如,在Go语言学习中,先理解包管理机制,再深入并发模型。
  1. 掌握语言基础:变量、控制结构、函数定义
  2. 理解核心特性:如Go的goroutine与channel
  3. 实践项目结构设计:合理划分package职责
  4. 引入测试驱动开发:编写单元测试保障代码质量
实战驱动能力跃迁
真实项目是检验技能的最佳场景。曾有开发者在构建高并发订单系统时,初期直接使用全局变量存储状态,导致数据竞争。通过引入sync.Mutex与context控制生命周期,显著提升稳定性。

func handleOrder(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()

    select {
    case orderQueue <- parseOrder(r):
        w.WriteHeader(http.StatusAccepted)
    case <-ctx.Done():
        http.Error(w, "timeout", http.StatusRequestTimeout)
    }
}
持续优化与工具链整合
进阶者需关注性能剖析与CI/CD集成。利用pprof定位内存瓶颈,结合golangci-lint实现静态检查自动化。下表展示常见工具的应用场景:
工具用途执行命令示例
go test -race检测数据竞争go test -race ./...
go tool pprof分析CPU与内存占用go tool pprof cpu.prof
# -*- coding: utf-8 -*- """ ============================ pytest 大师课:从入门精通 ============================ 版本:2.1 | 修复版 """ # ==================== # 第一部分:pytest 核心概念 # ==================== """ 1. 为什么选择 pytest? - 更简洁的测试代码(相比 unittest) - 强大的断言机制(无需记忆特定方法) - 灵活的夹具系统(fixtures) - 丰富的插件生态(1000+ 插件) - 智能测试发现与执行 2. 核心组件: • 测试发现:自动查找 test_*.py 文件 • 测试执行:并行、选择性和顺序执行 • 报告系统:多种格式输出 • 插件架构:高度可扩展 3. 安装与升级: pip install -U pytest pytest-html pytest-xdist pytest-cov pytest-mock """ # ==================== # 第二部分:基础到进阶示例 # ==================== import os # 导入操作系统接口模块,用于文件路径操作 import pytest # 导入pytest测试框架 import sys # 导入系统模块,用于访问Python解释器相关功能 from datetime import datetime # 导入日期时间模块,用于时间相关操作 from unittest.mock import MagicMock, patch # 导入mock模块,用于模拟对象和函数 # -------------------- # 1. 测试函数与断言 # -------------------- def test_addition(): """基础断言示例:验证基本算术运算""" assert 1 + 1 == 2, "加法计算错误" # 使用assert进行简单断言 def test_container_operations(): """容器操作断言:验证列表操作""" numbers = [1, 2, 3, 4, 5] # 定义测试数据列表 assert 3 in numbers # 检查元素是否在列表中 assert numbers == [1, 2, 3, 4, 5] # 检查列表是否相等 assert all(n > 0 for n in numbers) # 检查所有元素是否满足条件 assert any(n % 2 == 0 for n in numbers) # 检查是否有元素满足条件 def test_floating_point(): """浮点数近似断言:处理浮点数精度问题""" result = 0.1 + 0.2 # 计算0.1+0.2 assert result == pytest.approx(0.3, rel=1e-5) # 使用approx进行近似比较 # -------------------- # 2. 夹具(fixture)系统详解 # -------------------- @pytest.fixture(scope="module") # 定义模块级夹具,整个测试模块共享 def database_connection(): """模块级夹具 - 整个测试模块共享""" print("\n>>> 创建数据库连接 (模块级)") # 夹具初始化时打印信息 conn = {"status": "connected", "version": "1.2.3"} # 创建模拟数据库连接对象 yield conn # 返回连接对象,测试结束后继续执行清理 print("\n>>> 关闭数据库连接 (模块级)") # 夹具清理时打印信息 @pytest.fixture # 定义函数级夹具(默认作用域) def temporary_user(database_connection): # 依赖database_connection夹具 """函数级夹具 - 依赖其他夹具""" print("\n>> 创建临时用户") # 夹具初始化时打印信息 user = {"id": 1001, "name": "test_user", "created_at": datetime.now()} # 创建模拟用户 yield user # 返回用户对象,测试结束后继续执行清理 print("\n>> 删除临时用户") # 夹具清理时打印信息 def test_user_creation(temporary_user): # 使用temporary_user夹具 """测试用户创建:验证夹具功能""" assert temporary_user["id"] == 1001 # 验证用户ID assert "test" in temporary_user["name"] # 验证用户名包含特定字符串 # -------------------- # 3. 参数化高级用法 (修复版) # -------------------- def is_prime(n): """判断质数的函数:检查数字是否为质数""" if n < 2: # 小于2的数不是质数 return False for i in range(2, int(n ** 0.5) + 1): # 只需检查到平方根 if n % i == 0: # 如果可整除则不是质数 return False return True # 通过所有检查则是质数 # 修复的参数化测试 @pytest.mark.parametrize( # 参数化测试装饰器 "number, expected", # 参数名称 [ # 参数值列表 (2, True), # 普通参数 (3, True), # 普通参数 (4, False), # 普通参数 (17, True), # 普通参数 (25, False), # 普通参数 pytest.param(1, False, id="edge_case_1"), # 使用pytest.param定义特殊参数 pytest.param(0, False, id="edge_case_0"), # 使用pytest.param定义特殊参数 ], ids=lambda param: f"num_{param[0]}" if isinstance(param, tuple) else param.id # 智能ID生成函数 ) def test_prime_numbers(number, expected): """测试质数判断函数:验证is_prime函数""" assert is_prime(number) == expected # 验证函数结果与预期一致 # -------------------- # 4. 标记系统高级应用 # -------------------- @pytest.mark.slow # 标记为慢速测试 @pytest.mark.integration # 标记为集成测试 def test_external_api_call(): """集成测试:模拟外部API调用""" import time # 导入时间模块 time.sleep(1.5) # 模拟API调用延迟 assert True # 简单断言 @pytest.mark.skipif(sys.version_info < (3, 8), reason="需要Python 3.8+") # 条件跳过装饰器 def test_walrus_operator(): """测试海象运算符:验证Python 3.8+特性""" data = [1, 2, 3, 4, 5] # 测试数据 if (n := len(data)) > 3: # 使用海象运算符 assert n == 5 # 验证结果 @pytest.mark.xfail(sys.platform == "win32", reason="Windows平台已知问题") # 预期失败装饰器 def test_filesystem_case_sensitivity(): """测试文件系统大小写敏感性:验证不同OS行为差异""" with open("tempfile.txt", "w") as f: # 创建临时文件 f.write("test") # 写入内容 assert not os.path.exists("TEMPFILE.txt") # Linux/Mac大小写敏感 os.remove("tempfile.txt") # 清理测试文件 # -------------------- # 5. 异常处理与警告 # -------------------- def test_expected_exception(): """测试预期异常:验证异常处理""" with pytest.raises(ValueError) as exc_info: # 捕获预期异常 int("not_a_number") # 故意引发异常的操作 assert "invalid literal" in str(exc_info.value) # 验证异常信息 def test_warnings(): """测试警告捕获:验证警告处理""" with pytest.warns(DeprecationWarning): # 捕获预期警告 import warnings # 导入警告模块 warnings.warn("This is deprecated", DeprecationWarning) # 发出警告 # -------------------- # 6. Mock与猴子补丁 # -------------------- def fetch_weather_data(api_url): """获取天气数据的函数:模拟外部API调用""" # 实际实现会调用外部API return {"temp": 25, "condition": "sunny"} # 返回模拟数据 def test_mock_external_api(): """使用Mock替代外部API调用:验证mock功能""" # 创建Mock替代实际函数 with patch(__name__ + ".fetch_weather_data") as mock_weather: # 使用patch上下文管理器 mock_weather.return_value = {"temp": 30, "condition": "cloudy"} # 设置mock返回值 result = fetch_weather_data("https://weather-api.com") # 调用被mock的函数 assert result["temp"] == 30 # 验证mock结果 mock_weather.assert_called_once_with("https://weather-api.com") # 验证调用参数 # -------------------- # 7. 临时文件与目录 # -------------------- def test_create_temp_files(tmp_path): # 使用pytest内置的tmp_path夹具 """测试临时文件操作:验证文件系统功能""" # 创建临时目录结构 data_dir = tmp_path / "data" # 创建子目录路径 data_dir.mkdir() # 创建目录 # 创建文件 file_path = data_dir / "test.csv" # 创建文件路径 file_path.write_text("id,name\n1,Alice\n2,Bob") # 写入文件内容 # 验证内容 content = file_path.read_text() # 读取文件内容 assert "Alice" in content # 验证内容包含Alice assert "Bob" in content # 验证内容包含Bob # ==================== # 第三部分:高级工程实践 # ==================== # -------------------- # 8. 测试覆盖率分析 # -------------------- """ 使用pytest-cov生成覆盖率报告: 1. 基本报告:pytest --cov=my_module 2. HTML报告:pytest --cov=my_module --cov-report=html 3. 阈值控制:pytest --cov=my_module --cov-fail-under=90 """ # 被测代码 def calculate_discount(price, discount_percent): """计算折扣后的价格:业务逻辑函数""" if discount_percent < 0 or discount_percent > 100: # 验证折扣率范围 raise ValueError("折扣率必须在0-100之间") # 抛出异常 return price * (1 - discount_percent / 100) # 计算折扣后价格 # 测试用例 def test_discount_calculation(): """测试折扣计算:正常情况""" assert calculate_discount(100, 20) == 80 # 验证20%折扣 assert calculate_discount(50, 10) == 45 # 验证10%折扣 def test_discount_edge_cases(): """测试折扣计算:边界情况""" with pytest.raises(ValueError): # 验证异常 calculate_discount(100, -5) # 负折扣率 with pytest.raises(ValueError): # 验证异常 calculate_discount(100, 110) # 超过100%的折扣率 # -------------------- # 9. 并行测试执行 # -------------------- """ 使用pytest-xdist进行并行测试: 1. 安装:pip install pytest-xdist 2. 运行:pytest -n auto # 自动检测CPU核心数 3. 指定核心数:pytest -n 4 注意:确保测试是独立的,无共享状态 """ @pytest.mark.parametrize("x", range(10)) # 参数化测试,10组数据 def test_parallel_execution(x): """模拟大量可并行测试:验证并行执行能力""" assert x * 0 == 0 # 简单断言 # -------------------- # 10. 自定义夹具参数化 # -------------------- def generate_test_data(): """生成测试数据组合:创建参数化数据""" return [ # 返回测试数据列表 {"input": "admin", "role": "administrator"}, # 管理员数据 {"input": "user", "role": "standard"}, # 普通用户数据 {"input": "guest", "role": "limited"} # 访客数据 ] @pytest.fixture(params=generate_test_data()) # 参数化夹具 def user_account(request): # request提供参数信息 """参数化夹具:根据参数生成不同用户账户""" return request.param # 返回当前参数 def test_user_roles(user_account): # 使用参数化夹具 """测试不同用户角色:验证权限系统""" assert user_account["role"] in ["administrator", "standard", "limited"] # 验证角色 # ==================== # 第六部分:完整测试示例 # ==================== class UserManagementSystem: """用户管理系统模拟类:业务逻辑封装""" def __init__(self): """初始化用户系统""" self.users = {} # 用户存储字典 self.next_id = 1 # 下一个用户ID def add_user(self, name, email): """添加用户:创建新用户""" if not email or "@" not in email: # 验证邮箱格式 raise ValueError("无效的邮箱地址") # 无效邮箱抛出异常 user_id = self.next_id # 分配用户ID self.users[user_id] = {"name": name, "email": email} # 存储用户信息 self.next_id += 1 # ID自增 return user_id # 返回新用户ID def get_user(self, user_id): """获取用户信息:根据ID查询用户""" return self.users.get(user_id) # 返回用户信息或None def delete_user(self, user_id): """删除用户:根据ID删除用户""" if user_id not in self.users: # 检查用户是否存在 raise KeyError("用户不存在") # 不存在则抛出异常 del self.users[user_id] # 删除用户 @pytest.fixture # 定义用户系统夹具 def user_system(): """用户管理系统夹具:创建预配置的用户系统""" system = UserManagementSystem() # 创建用户系统实例 # 添加初始用户 system.add_user("Admin", "admin@example.com") # 添加管理员用户 return system # 返回系统实例 class TestUserManagement: # 测试类 """用户管理系统测试套件:完整业务逻辑测试""" def test_add_user(self, user_system): # 测试添加用户 """测试添加用户:验证用户创建功能""" user_id = user_system.add_user("Alice", "alice@example.com") # 添加新用户 assert user_id == 2 # 验证用户ID(初始用户已占1) assert user_system.get_user(2)["name"] == "Alice" # 验证用户名 def test_add_invalid_email(self, user_system): # 测试无效邮箱 """测试无效邮箱:验证异常处理""" with pytest.raises(ValueError) as excinfo: # 捕获预期异常 user_system.add_user("Bob", "invalid-email") # 添加无效邮箱用户 assert "无效的邮箱地址" in str(excinfo.value) # 验证异常信息 def test_delete_user(self, user_system): # 测试删除用户 """测试删除用户:验证用户删除功能""" user_system.delete_user(1) # 删除初始用户 with pytest.raises(KeyError): # 捕获预期异常 user_system.get_user(1) # 尝试获取已删除用户 @pytest.mark.parametrize("email", [ # 参数化测试 "test@example.com", # 标准邮箱 "user.name@domain.co", # 带点的邮箱 "unicode@例子.中国" # 国际化邮箱 ]) def test_valid_emails(self, user_system, email): # 测试有效邮箱格式 """参数化测试有效邮箱格式:验证多种邮箱格式""" user_id = user_system.add_user("Test", email) # 添加不同格式邮箱的用户 assert user_id > 1 # 验证用户创建成功 # ==================== # 修复后的执行部分 # ==================== if __name__ == "__main__": # 主程序入口 import subprocess # 导入子进程模块,用于执行外部命令 import os # 导入操作系统接口模块 import sys # 导入系统模块 # 获取当前文件所在目录的绝对路径 current_dir = os.path.dirname(os.path.abspath(__file__)) # 构建正确的pytest命令列表 command = [ "pytest", # pytest主命令 __file__, # 当前文件路径 "-v", # 详细输出模式 f"--html={os.path.join(current_dir, 'test_report.html')}", # HTML报告输出路径 f"--cov={current_dir}", # 指定覆盖率测量范围(当前目录) "--cov-report=html" # 生成HTML格式的覆盖率报告 ] # 添加Mac系统特定优化 if sys.platform == "darwin": # 检查是否为macOS系统 command.append("--durations=10") # 显示最慢的10个测试 command.append("--color=yes") # 启用彩色输出 # 打印执行信息 print("=" * 50) # 分隔线 print("执行测试命令:") # 标题 print(" ".join(command)) # 打印完整命令 print("=" * 50) # 分隔线 try: # 执行测试命令 result = subprocess.run(command, check=True) # 运行pytest命令 # 构建报告文件路径 report_path = os.path.join(current_dir, "test_report.html") # HTML报告路径 cov_path = os.path.join(current_dir, "htmlcov", "index.html") # 覆盖率报告路径 # 打印报告信息 print("\n" + "=" * 50) # 分隔线 print("测试报告已生成:") # 标题 print(f"• 测试报告: file://{report_path}") # 测试报告路径 print(f"• 覆盖率报告: file://{cov_path}") # 覆盖率报告路径 print("=" * 50) # 分隔线 # 退出程序,返回pytest的退出码 sys.exit(result.returncode) except subprocess.CalledProcessError as e: # 处理命令执行错误 print(f"测试执行失败,退出码: {e.returncode}") # 打印错误信息 print("请检查是否安装了必要插件: pip install pytest pytest-html pytest-cov") # 提示安装插件 sys.exit(e.returncode) # 返回错误码 except FileNotFoundError: # 处理pytest命令未找到错误 print("错误:未找到pytest命令") # 打印错误信息 print("请确保已安装pytest:pip install pytest pytest-html pytest-cov") # 提示安装 sys.exit(1) # 返回错误码 这是我给0基础同学讲的pytest课,我觉得不太好,重新设计下
09-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值