Python的pytest框架(4)--参数化测试

在 pytest 测试框架中,参数化测试(Parametrized Testing)意味着将一个测试用例设计为能够接受不同输入数据(参数)并分别执行,以验证被测试代码在面对多种情况时的行为是否符合预期。参数化测试的核心理念是通过复用相同的测试逻辑,但使用不同的输入数据集来增加测试覆盖率,减少代码重复,并提高测试的灵活性和效率。该篇文章就如何使用pytest进行参数化配置来深入解析:

目录

一、参数化测试概念

二、使用 pytest.mark.parametrize

1、基本用法

2、多层参数化:组合数据示例

3、从外部文件加载参数数据(重要)

4、结合 fixture 进行参数化

5、动态参数生成

6、参数化与条件跳过


一、参数化测试概念

参数化测试是一种软件测试策略,它允许测试人员或开发人员使用一组预定义的输入数据集合来运行相同的测试逻辑。这意味着一个测试用例可以被设计为接受不同参数,并根据这些参数执行相应的测试操作。这种方法有助于提高测试覆盖率,确保程序在多种数据条件下的行为正确性,同时减少了编写重复测试代码的工作量。

在参数化测试中,测试脚本保持不变,但其执行时使用的数据集可以灵活变化。这些数据集可能包括边界条件、异常情况、典型用户输入、负测试用例等。通过参数化,测试团队可以系统地遍历各种预期和非预期的输入情况,确保软件在面对多种输入时都能稳定、准确地响应。

二、使用 pytest.mark.parametrize

1、基本用法

pytest的参数化主要通过 pytest.mark.parametrize 装饰器实现。这个装饰器允许你为测试函数指定一组或多组不同的输入数据和预期输出(如果有),从而生成多个独立的测试用例。

基本用法如下:

import pytest# 假设有一个待测试的函数 `add(a, b)`
def add(a, b):return a + b# 使用 @pytest.mark.parametrize 装饰器参数化测试函数
@pytest.mark.parametrize("a, b, expected_sum", [(1, 2, 3),(0, 5, 5),(-1, -2, -3),# ... 更多测试数据
])
def test_add(a, b, expected_sum):result = add(a, b)assert result == expected_sum

在这个例子中,pytest.mark.parametrize 接受三个参数:

参数名:一个由逗号分隔的字符串列表,表示要传递给测试函数的参数名。在这个例子中是 "a, b, expected_sum"。

数据集:一个嵌套列表,其中每个内部元组对应一组测试数据。元组中的元素按照参数名的顺序与测试函数的参数对应。例如,元组 (1, 2, 3) 表示 a=1, b=2, expected_sum=3。

当 pytest 运行时,它会为每组数据生成一个单独的测试用例(我们写了三组数据也就是会生成三个单独的测试用例),并调用 test_add 函数,传入相应的参数值。这样,即使测试逻辑相同(即检查 add() 函数的输出是否等于预期和),由于使用了不同的输入数据,实际执行的是多个独立的测试。

通过 ids 关键字参数,可以为生成的测试用例提供易读的名称,尤其是在测试数据难以从参数值直接推断的情况下:

import pytest#场景一:简单字符串列表
test_data = [(1, 2), (3, 4), (5, 6)]
ids = ["Case1", "Case2", "Case3"]@pytest.mark.parametrize("a, b", test_data, ids=ids)
def test_multiply_numbers(a, b):assert a * b == a + b
'''
在这个例子中,ids 参数是一个包含三个字符串的列表,分别对应 test_data 中的三个元组。测试报告将显示为 "Case1", "Case2", "Case3" 而不是默认的参数值。
'''#场景二:基于参数值生成名称
test_data = [(1, 2, 3), (4, 5, 9)]@pytest.mark.parametrize("a, b, expected_sum", test_data, ids=lambda params: f"a={params[0]}_b={params[1]}_sum={params[2]}")
def test_addition(a, b, expected_sum):assert a + b == expected_sum
'''
这里的 ids 参数是一个 lambda 函数,它接收每个参数元组 params,并使用 f-string 格式化输出一个描述性的字符串,如 "a=1_b=2_sum=3"。这样,测试报告中的用例名称将明确反映每个测试用例的具体参数值。
'''#场景三:自定义命名规则
def generate_id(data):username, password = datareturn f"{username}_{password[:3]}_login"test_data = [("user1", "pass123"), ("user2", "passabc")]@pytest.mark.parametrize("username, password", test_data, ids=generate_id)
def test_login(username, password):# 实现登录逻辑的断言pass
'''
在这个例子中,generate_id 函数接收包含用户名和密码的元组,返回一个如 "user1_pass1_登录" 或 "user2_passa_登录" 样式的字符串。这个自定义函数被直接用作 ids 参数,为每个测试用例生成具有特定格式的名称。
'''#场景四:处理中文及其他非 ASCII 字符
test_data = [(1, "你好"), (2, "世界")]
ids = [f"Case_{i}_({text})".encode("utf-8") for i, text in enumerate(test_data, start=1)]@pytest.mark.parametrize("num, text", test_data, ids=ids)
def test_chinese_text(num, text):assert isinstance(text, str)
'''在这个例子中,ids 列表中的每个字符串都被显式地 encode() 成 UTF-8 编码。如果控制台能够正确处理 UTF-8 输出,则无需解码;否则,可能需要在显示时进行 decode()。'''

2、多层参数化:组合数据示例

有时需要对多个参数进行不同的组合,形成复杂的测试矩阵。这可以通过应用多个 pytest.mark.parametrize 装饰器来实现。它们提供的数据集会按照笛卡尔积的方式组合,生成更复杂的测试用例矩阵:

def multiply(a, b):return a * b# 参数化 `a` 的数据
a_values = [-2, 0, 1, 2]# 参数化 `b` 的数据
b_values = [0, 1, 2, 3]# 为 `a` 和 `b` 分别应用参数化,生成所有组合的测试用例
@pytest.mark.parametrize("a", a_values)
@pytest.mark.parametrize("b", b_values)
def test_multiply(a, b):expected_product = a * bassert multiply(a, b) == expected_product

在这个示例中,我们分别为 a 和 b 定义了参数化数据。每个参数的装饰器都会生成一系列测试用例。由于两个装饰器同时作用于 test_multiply() 函数,pytest 会按照笛卡尔积的方式组合 a 和 b 的所有可能值,生成 4 × 4 = 16 个测试用例。

3、从外部文件加载参数数据(重要)

在实际项目中,参数数据可能会非常多,或者需要根据实际情况动态调整。此时,可以从外部文件(如 CSV、JSON)加载参数数据。使用外部文件通常会需要使用第三方库来操作读取文件数据:

import csv
import pytest# 假设有一个 `test_data.csv` 文件,内容如下:
# a,b,expected_result
# 1,2,3
# 4,5,20
# ...def load_test_data(file_path):test_data = []with open(file_path, newline='') as csvfile:reader = csv.DictReader(csvfile)for row in reader:test_data.append((int(row['a']), int(row['b']), int(row['expected_result'])))return test_datatest_data = load_test_data('test_data.csv')@pytest.mark.parametrize("a, b, expected_result", test_data)
def test_multiply_from_file(a, b, expected_result):assert multiply(a, b) == expected_result

这里,我们定义了一个 load_test_data() 函数,它从指定的 CSV 文件中读取参数数据,并将其转换为一个包含 (a, b, expected_result) 元组的列表。然后,将这个列表作为参数数据传递给 pytest.mark.parametrize。这样,测试数据就可以独立于测试代码进行管理和更新。

4、结合 fixture 进行参数化

pytest 的 fixture 可以用来提供测试所需的共享资源或预置条件。结合参数化,可以动态生成 fixture 数据。

import pytest@pytest.fixture(params=[1, 2, 3, 4, 5])
def input_value(request):return request.paramdef test_with_fixture(input_value):assert input_value > 0

在这个例子中,我们定义了一个 fixture input_value,并使用 params 参数对其进行参数化。pytest 会为 fixture 指定的每个参数值生成一个独立的 fixture 实例,并将其注入到使用该 fixture 的测试函数中。因此,test_with_fixture() 函数会被执行五次,每次使用 input_value fixture 提供的一个不同的正整数。

5、动态参数生成

除了使用静态定义的参数数据集,还可以编写函数或使用生成器来动态生成参数值。这在处理大量数据、随机数据或基于某种规则生成的测试数据时特别有用,以下是一个简单的例子:

import random
import pytestdef generate_random_integers(count=10):return [(random.randint(-100, 100), random.randint(-100, 100)) for _ in range(count)]@pytest.mark.parametrize("x, y", generate_random_integers())
def test_complex_operation(x, y):result = complex_operation(x, y)assert result.is_valid()  # 假设有一个is_valid()方法来验证结果有效性

对于需要大量或复杂参数组合的场景,可以使用生成器函数(yield 语句)来动态生成参数。这种方式特别适用于有特定规律或算法生成的参数集:

import pytest
import randomdef generate_random_inputs():for _ in range(10):input = random.randint(1, 10**999)expected = input * 2yield input, expected@pytest.mark.parametrize("input, expected", generate_random_inputs())
def test_doubling_function(input, expected):assert doubling_function(input) == expected

在这个例子中,generate_random_inputs 是一个生成器函数,它每次 yield 一对随机的 input 和对应的 expected 值。parametrize 会遍历这些生成的值,为每一对生成一个测试实例。

6、参数化与条件跳过

可以结合 pytest.mark.skipif 或 pytest.mark.xfail 根据特定条件(如环境变量、版本依赖等)有条件地跳过某些参数组合或标记其为预期失败。

import pytest@pytest.mark.parametrize("input, expected", ...)
@pytest.mark.skipif(condition, reason="由于缺少依赖项而跳过")
def test_feature(input, expected):...@pytest.mark.parametrize("input, expected", ...)
@pytest.mark.xfail(condition, reason="由于已知问题导致的预期故障")
def test_flaky_behavior(input, expected):...

condition是一个布尔值或可以计算出布尔值的表达式,pytest 在执行测试之前会先评估这个条件。当条件为 True 时,即使测试实际通过了,pytest 也会将其报告为预期失败(xfail)。反之,若条件为 False,则测试按照正常流程执行,无论其实际结果是通过还是失败。

也可以是一个可调用对象(如函数、lambda 表达式等),pytest 会在测试执行前调用它。该可调用对象应不接受任何参数,并返回一个布尔值。返回 True 时,测试被视为预期失败;返回 False 时,测试按正常流程执行。

希望以上内容能帮助大家理解使用pytest进行参数化操作!

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

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

相关文章

DAY28| 93. 复原IP地址 ,79.子集 ,90.子集II

文章目录 93.复原IP地址78.子集90.子集II 93.复原IP地址 文字讲解:复原IP地址 视频讲解:复原IP地址 **状态:**此题调试了几次ok,与昨天的分割回文子串相比,就是在判断终止条件处需要处理; 思路:…

设计模式- 单例模式(Singleton)结构|实现方式|优缺点|场景

目录 设计模式(分类) 设计模式(六大原则) 创建型 工厂方法 抽象工厂模式 单例模式 建造者模式 单例模式(Singleton Pattern)是一种创建型设计模式,其…

rust : condvar中一对一和多对一模式初探

condvar是不经常碰到的,但其实在tokio之类库中,还是非常核心的作用。 想进一步体会condvar的使用,还是从场景出发。 一、一个通知发送者,一个接收者 假定一个员工收到一个任务,就是模拟是一个时间片,到时…

【Qt】设置QT标准对话框为中文字体

设置QT标准对话框为中文字体 一、问题二、解决方法1、找到Qt内置的翻译文件 qt_zh_CN.qm2、在代码中加载该文件 一、问题 在Qt中我们使用的标准对话框都是英文,例如下面的 字体选择对话框,但是实际中我们需要构建的是中文对话框。 所以我们需要使用Qt官…

19篇 vue3进阶

一 基础特性 1. **Composition API**:引入了组合式 API,允许以函数的方式组织组件逻辑。 2. **响应式系统**:使用 Proxy 作为其响应式系统的基础,提供更精确的依赖追踪。 3. **模板语法**:保留了 Vue 2 的模板语法…

MySQL InnoDB事务隔离级别与锁机制深入解析

引言 在当今的数据库系统中,事务管理是确保数据一致性和完整性的关键。事务是数据库操作的基本单元,它将一系列的数据库操作组合成一个逻辑工作单元,要么全部成功执行,要么全部失败回滚,这就是所谓的ACID属性&#xf…

js自动缩放页面,html自动缩放页面,大屏自动缩放页面,数字看板自动缩放页面,大数据看板自动缩放页面

js自动缩放页面&#xff0c;html自动缩放页面&#xff0c;大屏自动缩放页面&#xff0c;数字看板自动缩放页面&#xff0c;大数据看板自动缩放页面 由纯JS实现 html代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"…

vue 注册自定义指令,对输入框输入内容过滤(区分中英文)

注册vue全局指令 对el-input输入框进行最大长度过滤&#xff08;区分中英文&#xff09; 过滤空格 注册全局指令 main.js /*** 输入框最长输入限制* param {*} e 文本内容* param {number} maxlength 最大字符长度* param {boolean} trim 是否过滤空格* returns {string} 最…

acwing算法提高之图论--有向图的强连通分量

目录 1 介绍2 训练 1 介绍 本博客介绍有向图的强连通分量的题目。 连通分量&#xff1a;是针对有向图的一个概念。对于分量中任意两个结点a、b&#xff0c;必然可以从a走到b&#xff0c;且从b走到a。 强连通分量&#xff1a;是针对有向图的一个概念。极大强连通分量&#xff…

【数据结构】单链表的头节点与尾节点

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;数据结构 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

玄子Share-计算机网络参考模型

玄子Share-计算机网络参考模型 分层思想 利用七层参考模型&#xff0c;便于在网络通信过程中&#xff0c;快速的分析问题&#xff0c;定位问题并解决问题 将复杂的流程分解为几个功能相对单一的子过程 整个流程更加清晰&#xff0c;复杂问题简单化 更容易发现问题并针对性的…

【Java开发指南 | 第十七篇】Java 方法

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 简介语法实例构造方法 简介 Java中的方法是用来执行特定任务的一组语句&#xff0c;可以重复使用。它们包含在类或对象中&#xff0c;并通过调用来执行。 举个例子&#xff0c;println() 是一个方法&#xff…

动态内存管理 柔性数组

文章目录 动态内存函数 malloc freecallocrealloc 重新开辟空间realloc 也可以第一个参数为NULL&#xff0c;则是直接开辟内存&#xff0c;类似于malloc用法 常见的动态内存错误对空指针进行解引用操作对开辟的内存越界访问对非动态开辟的内存使用free释放使用free释放动态开辟…

vue2 在循环里,给字体加上随机颜色并加上随机图标且少重复

在循环里&#xff0c;给字体加上随机颜色并加上随机图标且少重复 <template><div class"pbfb5"><el-row :gutter"32"><el-col :xs"6" :sm"6" :lg"6" style"margin-bottom:32px;" v-for&quo…

(四)相关性分析 学习简要笔记 #统计学 #CDA学习打卡

目录 一. 相关性分析简介 二. 相关性分析方法 1&#xff09;连续型变量vs连续型变量&#xff1a;Pearson/Spearman &#xff08;a&#xff09;Pearson &#xff08;b&#xff09;Spearman等级相关系数 2&#xff09;二分类变量&#xff08;自然&#xff09;vs连续型变量&…

macos知名的清理软件 cleanmymac和腾讯柠檬哪个好 cleanmymacx有必要买吗

MacOS是一款优秀的操作系统&#xff0c;但是随着使用时间的增加&#xff0c;它也会产生一些不必要的垃圾文件&#xff0c;占用磁盘空间和内存资源&#xff0c;影响系统的性能和稳定性。为了保持MacOS的清洁和高效&#xff0c;我们需要使用一些专业的清理软件来定期扫描和清除这…

CentOS服务器安装宝塔(图文详解)

宝塔的操作其实就是类似于把linux的指令使用方式&#xff0c;通过宝塔这个第三方工具进行可视化展示&#xff0c;但其实&#xff0c;他还是在操作linux&#xff0c;只是不需要你去记那么多的指令&#xff0c;宝塔把大多数的工具都集成到自己里面&#xff0c;这样你就可以在宝塔…

信号量Semaphore

什么是信号量&#xff1f; C中的信号量&#xff08;Semaphore&#xff09;是一种同步对象&#xff0c;用于控制对共享资源的访问&#xff0c;以防止多个线程或进程同时访问同一资源&#xff0c;从而避免数据不一致的问题。信号量通过维护一个计数值来实现这一功能&#xff0c;…

【Golang】Gin教学-获取请求信息并返回

安装Gin初始化Gin处理所有HTTP请求获取请求的URL和Method获取请求参数根据Content-Type判断请求数据类型处理JSON数据处理表单数据处理文件返回JSON响应启动服务完整代码测试 Gin是一个用Go&#xff08;又称Golang&#xff09;编写的HTTP Web框架&#xff0c;它具有高性能和简洁…

Event loop(Message loop)

事件循环&#xff08;消息循环&#xff09; 浏览器的进程模型 进程 程序运行需要有它自己专属的内存空间&#xff0c;可以把这块内存空间简单的理解为进程 每个应用至少有一个进程&#xff0c;进程之间相互独立&#xff0c;即使要通信&#xff0c;也需要双方同意。 线程 …