首页 > 其他分享 >pytest的数据驱动和参数传递

pytest的数据驱动和参数传递

时间:2024-06-10 17:33:45浏览次数:13  
标签:parametrize 参数传递 expected pytest 参数 input test 驱动

4.1参数化介绍

常见使用场景:简单注册功能,也就是输入用户名、输入密码、单击注册,而测试数据会有很多个,可以通过测试用例设计技术组织出很多测试数据,例如用户名都是字母,密码也都是字母,或者都是数字,也可是它们的组合,或是边界值长度的测试数据等。

这时可以通过参数化技术实现测试数据驱动执行每组测试用例。测试数据与测试用例是多对一的关系,所以完全可以把它们分开来看,把数据部分抽象成参数,通过对参数的赋值来驱动用例的执行。参数化传递是实现数据驱动的一种技术,可以实现测试数据与测试用例分离。

各个方面的参数化如下:

  • 测试用例的参数化:使用@pytest.mark.parametrize可以在测试用例、测试类甚至测试模块中标记多个参数或fixture的组合;
  • 参数化的行为可以表现在不同的层级上;
  • 多参数的参数化:一个以上参数与数据驱动结果;
  • 自定义参数化:可以通过pytest_generate_tests这个钩子方法自定义参数化的方案;
  • 使用第三方插件实现数据驱动DDT。

4.2参数化的应用

通过@pytest.mark.parametrize可以实现数据驱动。@pytest.mark.parametrize的根本作用是在收集测试用例的过程中,通过对指定参数的赋值来新增被标记对象的调用(执行)。下面以例说明具体参数化时如何使用不同数据。

4.2.1 单一参数化应用

通常使用场景:测试方法中只有一个数据是变化的,也就是通过一个参数把多组测试数据传递进去。执行时,每组数据都执行一遍。实现的具体步骤如下:

(1)在测试方法中输入@pytest.mark.parametrize。

(2)其中有两个参数,一个是参数名,另一个是参数值,这个值可以是多个,可以是数字或字符等。

(3)测试方法中的参数与parametrize中的参数名相同。

(4)通过参数名在测试方法中调用这些数据。

代码如下:

import pytest


@pytest.mark.parametrize("test_case", [1, 2, 3, "one", "two"])
def test_string(test_case):
    print(f"\n 测试数据{test_case}")

一个测试用例,有多少条数据就自动执行多少遍。执行的结果如下:

4.2.2 多参数应用

测试输入的数据可以是表达式,输入的参数可以是多个。多个数据可以通过元组方式组织。下面是一个测试计算器的简单例子,前面两个是变量,后面是对应的数据。3+5对应的是test_input参数名,8对应的是expected参数名,下面的数据以此类推。eval将字符串str

当成有效表达式来求值并返回计算结果。

代码如下:

import pytest


@pytest.mark.parametrize("test_input, expected", [("3+5", 8),("2+5", 7),("9+5", 80)])
def test_eval(test_input,expected):
    assert eval(test_input) == expected

将其中一组数据写成错误的形式,验证断言的详细情况。执行结果如下:

4.2.3 多个参数化

一个用例可以标记多个@pytest.mark.parametrize标记。

代码如下:

import pytest


@pytest.mark.parametrize("test_input", [1, 2, 3])
@ pytest.mark.parametrize("test_output, expected", [(1, 2), (3, 4)])
def test_multi(test_input, test_output, expected):
    print(f"\n 测试结果{test_input}--{test_output}--{expected}")

实际收集到的用例是它们所有可能的组合。

4.2.4 参数化与fixture的结合

当一个测试方法既是注入依赖,也就是使用fixture,同时又要参数化时,使用parametrize会有冲突,此时可以通过fixture自带的参数params实现参数化。这也是参数化的一种方法。

4.2.5 pytestmark实现参数化

可以尝试通过对pytestmark赋值,参数化一个测试模块。

代码如下:

import pytest

pytestmark = pytest.mark.parametrize("test_input, expected", [(1, 2), (3, 4)])


def test_module(test_input, expected):
    assert test_input + 1 == expected

结果如下:

4.3parametrize源码详细讲解

下面通过两个例子讲解参数化技术。我们先来看一下它在源码中的定义。此方法在structures.py文件中。

源码及部分翻译如下:

4.4argnames参数

183parametrize方法中的第一个参数argnames是一个用逗号分隔的字符串,或者一个列表/元组,表明指定的参数名。argnames通常是与被标记测试方法入参的参数名对应的,但实际上有一些限制,它只能是被标记测试方法入参的子集。

4.4.1 argnames与测试方法中的参数关系

1.测试方法未声明,mark.parametrize中声明

test_sample1中并没有声明expected参数,如果在标记中强行声明,则会得到如下错误。

代码如下:

import pytest


@pytest.mark.parametrize("input, expected", [(1, 2)])
def test_samplel(input):
    assert input + 1 == 1

执行的结果会提示下面所示的错误信息:

In test_samplel: function uses no argument 'expected'

2.测试方法参数声明的范围小于mark.parametrize中声明的范围

不能是被标记测试方法入参中定义了默认值的参数。

代码如下:

import pytest


@pytest.mark.parametrize("input, expected", [(1, 2)])
def test_samplel2(input, expected=2):
    assert input + 1 == expected

虽然test_sample2声明了expected参数,但同时也为其赋予了一个默认值,如果非要在标记中强行声明,则会得到如下错误:

In test_samplel2: function already takes an argument 'expected' with a default value

4.4.2 argnames调用覆盖同名的fixture

通常在使用fixture和参数parametrize时,可以一个参数使用参数化,另一个参数使用fixture和参数化,而同时使用fixture和参数化时,参数化的参数值会覆盖原来fixture返回的值。

代码如下

import pytest


@pytest.fixture()
def expected():
    return 2


@pytest.fixture()
def input():
    return 0


@pytest.mark.parametrize("input", [(1)])
def test_sanple(input, expected):
    assert input + 1 == expected

执行结果:

可以看到expected参数未使用参数化传入数据,而是直接调用fixture中的返回值2,input同时使用参数化和fixture,参数化中参数值1覆盖了原来fixture的返回值0,因此执行结果断言应该是成功的。

 

参数化的参数可以不是fixture的,因此可以通过参数值传入。

代码如下:

@pytest.fixture()
def expected():
    return 1


@pytest.mark.parametrize("input,expected", [(1, 2)])
def test_sanple(input,expected):
    assert input + 1 == expected

test_sample标记的input参数的值是由后面的(1,2)传入的,expected参数(参数值为2)覆盖了同名的fixture expected(返回值1),所以这条用例是可以测试成功的。

4.5argvalues参数

参数化中参数值argvalues是一个可迭代对象,表明对argnames参数的赋值,具体有以下几种情况:如果argnames包含多个参数,那么argvalues的迭代返回元素必须是可度量的值,即支持len()方法,并且长度和argnames所声明参数的个数相等,所以它可以是元组/列表/集合等,表明所有入参的实参。

代码如下:

import pytest



@pytest.mark.parametrize("input,expected", [(1, 2), {2, 3}, set([3, 4])])
def test_sanple4(input, expected):
    print(expected)
    assert input + 1 == expected

执行结果如下:

4.5.1 argvalues来源于Excel文件

argvalues是一个可迭代对象,所以可以应用在更复杂的场景中,这在实际应用中被特别广泛使用。公司一般会将测试数据保存在Excel表中,或csv文件中,或数据库中。可以先

将数据读取到列表中,这样便可以在参数化的参数值中直接调用。例如:从Excel文件中读取实参。

代码如下:

import pytest


def read_excel():
    # 从数据库或者excel中读取数据信息,这里简化成一个列表
    for dev in ["dev1", "dev2", "dev3"]:
        yield dev


@pytest.mark.parametrize("dev", read_excel())
def test_sample5(dev):
    print(dev)

执行结果如下:

4.5.2 使用pytest.param为argvalues赋值

在结合pytest.param方法对skip和xfail标记中,可以使用pytest.param为argvalues参数赋值,让执行有更详细说明。

代码如下:

import pytest


@pytest.mark.parametrize(("n", "expected"), [(4, 2), pytest.param(6,3,marks=pytest.mark.xfail(), id="XPASS")])
def test_param(n,expected ):
    assert  n/2 == expected

执行结果:

无论argvalues中传递的是可度量对象(列表、元组等)还是具体的值,在源码中都会将其封装成一个ParameterSet对象,它是一个具名元组(namedtuple),包含values、marks、id 3个元素,代码如下:

如果直接传递一个ParameterSet对象会发生什么呢?源码如下:

可以看到,如果直接传递一个ParameterSet对象,那么返回的就是它本身(returnparameterset),所以下面例子中的两种写法是等价的。

 

pytest.param的作用就是封装一个ParameterSet对象。源码如下:

4.6indirect参数

indirect是argnames的子集或者一个布尔值。将指定参数的实参通过request.param重定向到和参数同名的fixture中,以此满足更复杂的场景。默认indirect为False,使用mark.parametrize后的数据。当indirect为True时,使用fixture中的数据。

代码如下:

import pytest


@pytest.fixture()
def max(request):
    print("max", request.param)
    return request.param - 1


@pytest.fixture()
def min(request):
    return request.param + 1


# 默认indirect为False,min和max使用的后面的数据,
@pytest.mark.parametrize("min, max", [(1, 2), (3, 4)])
def test_indirect(min, max):
    assert min <= max


# min和max对于的实参重定向重名的fixture中,min和max使用的是fixture的数据
@pytest.mark.parametrize("min, max", [(1, 2), (3, 4)], indirect=True)
def test_indirect2(min, max):
    assert min >= max


# 只将max对应的实参重定向fixture中,min使用的后面的数据,max使用的是fixture的数据
@pytest.mark.parametrize("min, max", [(1, 2), (3, 4)], indirect=["max"])
def test_indirect3(min, max):
    assert min == max

indirect=True,min和max对应的实参重定向到同名的fixture中,min和max使用的是fixture的数据。

indirect=['max'],只将max对应的实参重定向到fixture中,min使用的是后面的数据,max使用的是fixture的数据。

其实这是一种间接参数化的方式,当indirect=True时,允许在将值传递给测试之前使用接收值的fixture对测试进行参数化。

4.7ids参数

ids参数就是id,因为与关键字雷同所以不能用,因此改成ids。通常不写ids时每次不同数据直接显示,也就是数据本身,如果定义ids值,则显示的就是这个值。大家可以通过在ids中写内容来标记我们的测试要点。通常我们在测试时分测试数字、字母、边界值等,

因此我们可以通过对这个参数的设置检查是不是覆盖全面。例如第1个数据是数字,第2个数据是中文,第3个数据是特殊字符。这样在报告中看到结果就知道是否测试完整。

ids是一个可执行对象,用于生成测试id,或者一个列表/元组,指明所有新增用例的测试id。这些id可用于-k选择要运行的特定用例,当某个用例失败时,它们还将识别该特定用例。运行pytest--collect-only将显示生成的id。

4.7.1 ids的长度

如果使用列表/元组直接指明测试id,那么它的长度等于argvalues的长度。

代码如下:

import pytest


@pytest.mark.parametrize("input, expected", [(1, 2), (3, 4)], ids=["first", "second"])
def test_ids_1(input, expected):
    pass

执行结果:

input参数的id是first,第1次的值是1,第2次的值是3,expected参数的id是second,第1次的值是2,第2次的值是4。

4.7.2 ids相同

如果测试id相同,pytest则会在后面自动添加索引,例如[num0]和[num1]。

@pytest.mark.parametrize("input, expected", [(1, 2), (3, 4)], ids=["num", "num"])
def test_ids_2(input, expected):
    pass

执行结果:

4.7.3 ids中使用中文

测试ID中可以使用中文,默认显示的是字节序列。

@pytest.mark.parametrize("input, expected", [(1, 2), (3, 4)], ids=["num", "中文"])
def test_ids_3(input, expected):
    pass

收集到的测试ID如下:

 

从上面的结果可以看出,期望显示“中文”,但实际上显示的是\u4e2d\u6587。如何解决此问题?

源码如下:

解决中文乱码,可以在pytest.ini中将disable_test_id_escaping_and_forfeit_all_rights_to_community_support 选 项 设 置 为True。

代码如下:

再次收集到的测试ID如下:

4.7.4 通过函数生成ids

import pytest


def idfn(val):
    return val+1


@pytest.mark.parametrize("input, expected", [(1, 2), (3, 4)], ids=idfn)
def test_ids_4(input, expected):
    pass

执行结果显示如下:

通过上面的例子不难看出,对于一个具体的argvalues参数(1,2)来讲,它被拆分为1和2分别传递给idfn,并将返回值通过-符号连接在一起,以此作为一个测试id返回,而不是将(1,2)作为一个整体传入。

源码如下:

先通过zip(parameterset.values,argnames)将argnames和argvalues的值一一对应,再将处理过的返回值通过"-".join(this_id)连接。

4.7.5 ids的覆盖

假设已经通过pytest.param指定了id属性,那么将会覆盖ids中对应的测试id。

代码如下:

执行结果如下:

测试id是id_via_pytest_param,而不是second。

4.7.6 ids的作用

ids最主要的作用就是更进一步细化测试用例,区分不同的测试场景,为有针对性的执行测试提供了一种新方法。

例如,对于以下测试用例,可以通过-k'Non-Windows'选项,只执行和Non-Windows相关的场景。

代码如下:

import pytest


@pytest.mark.parametrize("input, expected", [
    pytest.param(1, 2, id="windows"),
    pytest.param(3, 4, id="windows"),
    pytest.param(5, 6, id="no-windows")
])
def test_ids6(input, expected):
    pass

执行结果:

4.8 scope参数

scope参数声明argnames中参数的作用域,并通过对应的argvalues实例划分测试用例,进而影响测试用例的收集顺序。

4.8.1 module级别

如果我们显式地指明scope参数,例如,将参数作用域声明为模块级别,这样设置后测试方法会进行一起统筹,也就是执行的顺序是先执行所有测试方法的第一组数据,再整体执行第二组数据,直到执行完成。

代码如下:

import pytest


@pytest.mark.parametrize("test_input, expected", [(1, 2), (3, 4)], scope="module")
def test_scopt1(test_input, expected):
    pass


@pytest.mark.parametrize("test_input, expected", [(1, 2), (3, 4)], scope="module")
def test_scopt2(test_input, expected):
    pass

执行结果:

当未将scope设置为module时,默认的收集顺序是按测试方法的先后执行的。也就是先执行第一个测试方法中的所有数据,再执行第二测试方法中的所有数据。

4.8.2 未指定scope

在scope未指定的情况下(或者scope=None),当indirect被设置为True或者包含所有的argnames参数时,作用域为所有fixture作用域的最小范围,否则,其永远为function。

import pytest


@pytest.fixture(scope="module")
def test_input(request):
    pass


@pytest.fixture(scope="module")
def expected(request):
    pass


@pytest.mark.parametrize("test_input, expected", [(1, 2), (3, 4)], indirect=True)
def test_scopt1(test_input, expected):
    pass


@pytest.mark.parametrize("test_input, expected", [(1, 2), (3, 4)], indirect=True)
def test_scopt2(test_input, expected):
    pass

test_input和expected的作用域都是module,所以参数的作用域也是module

结果如下:

4.9 pytest_generate_tests钩子方法

pytest实现参数化有3种方式:
·pytest.fixture()使用fixture传params参数实现参数化;
·@pytest.mark.parametrize允许在测试函数或类中定义多组参数;
·pytest_generate_tests允许定义自定义参数化方案或扩展。
本节简单介绍自定义参数化方案。pytest_generate_tests在测试用例参数化收集前调用此钩子函数,根据测试配置或定义测试函数的类或模块中指定的参数值生成测试用例,可以使用此钩子实现自定义参数化方案或扩展。

有时可能要实现自己的参数化方案或实现某种动态性来确定fixture的参数或范围,因此,可以使用pytest_generate_tests在收集测试函数时调用的钩子。通过传入的metafunc对象,可以检查请求的测试上下文,最重要的一点是,可以调用metafunc.parametrize()引起参数化。

我们先看一看源码中是怎么使用这种方法的。

源码如下:

首 先 , 它 检 查 了 parametrize 的 拼 写 错 误 , 如 果 不 小 心 将 parametrize 写 成 了["parameterize","parametrise","parameterise"]中的一个,pytest会返回一个异常,并提示正确的单词,然后循环遍历所有的parametrize的标记,

并调用metafunc.parametrize方法。例如,假设我们要运行一个测试,并接收通过新的pytest命令行选项设置的字符串输入。我们首先需要编写一个接收stringinput函数参数的简单测试。我们检查给定的stringinput是否只由字母组成,

但是我们并没有为其打上parametrize标记,所以stringinput被认为是一个fixture。

代码如下

现在,我们期望把stringinput当成一个普通的参数,并且从命令行赋值。

首先,我们定义一个命令行选项。

代码如下:

然 后 , 我 们 通 过 pytest_generate_tests 方 法 , 将 stringinput 的 行 为 由 fixture 改 成parametrize。

代码如下:

最后,我们可以通过--stringinput命令行选项为stringinput参数赋值。

代码如下:

如果我们不加--stringinput选项,相当于parametrize的argnames中的参数没有接收到任何的实参,那么测试用例的结果将会被置为SKIPPED。

不管是metafunc.parametrize方法还是@pytest.mark.parametrize标记,它们的参数(argnames)不能是重复的,否则会产生一个错误:ValueError:duplicate 'stringinput'。

标签:parametrize,参数传递,expected,pytest,参数,input,test,驱动
From: https://www.cnblogs.com/sanfenguiyuan/p/18239807

相关文章

  • 按键中断驱动程序-异步通知
    在前面的休眠-唤醒、POLL机制中,都是通过休眠等待某一个事件的发生,而程序一旦陷入休眠,就没法再执行其它任务,相当于整个程序卡死了。在很多的场景中,如果发生了某一个事件我们就去处理它,没有发生事件那就可以做其它的事情。这种正常执行程序,当中断发生时才去执行的方式就叫做异步通知......
  • SAMSUNG SCX4521F (4x21系列) 在MacOS Sonoma下的驱动问题!
    直接整就这种打印机,很经典First,youneedtodownloadthelegacySamsungPrinterDriver2.6forOSX-fromapplewebsite:SamsungPrinterDriversv2.6forOSXMountthedmgCopy.pkgfiletoyourDesktoporsomethingOpenterminalrun`pkgutil--expand~/D......
  • 【驱动】Linux内核调试之使用模块参数
    环境:处理器架构:arm64内核源码:linux-6.6.29ubuntu版本:20.04.1代码阅读工具:vim+ctags+cscope本文主要介绍内核开发中常用的模块传参手段,通过模块参数传递可以通过用户态来获取内核的一些信息,也可以通过用户态写入一些值来控制内核相关行为。一般内核开发者很喜欢使用模块传参......
  • 嵌入式Linux中驱动程序的基本框架
    在“嵌入式Linux中内核模块的基本框架”一文中,已经构建好了内核模块的基本框架结构,现在在该框架的基础上进一步扩展,就可以形成Linux下的字符型设备驱动基本框架,下面就详细进行讨论。在Linux系统中,设备驱动共分为三种类型,即字符型、块型和网络型。字符型设备以字节为最小操作单位,......
  • WDF驱动开发-PNP和电源管理(一)
    默认情况下,WDF框架处理系统发送到基于框架的驱动程序的所有PnP和电源管理请求。此外,默认情况下,仅当驱动程序的硬件可用且处于工作(D0)状态时,框架才会向函数驱动程序传递I/O请求。编写基于WDF框架的驱动程序时,可以使用WDF框架的大部分默认行为轻松支持设备的PnP和电源......
  • WDF驱动开发-PNP和电源管理(三)
    对于PNP设备来说,理解它们的启动和删除顺序,以及意外移除顺序非常重要,在早期,经常有拔插U盘导致windows重启的例子,这就是意外移除带来的问题。功能或Filter驱动程序的启动顺序下图显示了框架调用WDF(KMDF和UMDFV2)功能或Filter驱动程序的事件回调函数的顺序,从图底部的“设......
  • VMware ESXi 8.0U2c macOS Unlocker & OEM BIOS 集成网卡驱动 Marvell AQC 网卡定制版
    VMwareESXi8.0U2cmacOSUnlocker&OEMBIOS集成网卡驱动MarvellAQC网卡定制版VMwareESXi8.0U2cmacOSUnlocker&OEMBIOS集成网卡驱动和NVMe驱动(集成驱动版)发布ESXi8.0U2集成驱动版,在个人电脑上运行企业级工作负载请访问原文链接:VMwareESXi8.0U2cmacOS......
  • 【机器学习】与【数据挖掘】技术下【C++】驱动的【嵌入式】智能系统优化
    目录一、嵌入式系统简介二、C++在嵌入式系统中的优势三、机器学习在嵌入式系统中的挑战四、C++实现机器学习模型的基本步骤五、实例分析:使用C++在嵌入式系统中实现手写数字识别1.数据准备2.模型训练与压缩3.模型部署六、优化与分析1.模型优化模型量化模型剪枝......
  • Renesas MCU之SCI_SPI接口驱动LCD
    目录概述1软硬件介绍1.1软件版本信息1.2 ST7796-LCD1.3 MCUIO与LCDPIN对应关系2FSP配置项目2.1配置项目参数2.2生成项目框架3代码实现 3.1SPI的库函数3.1.1R_SCI_SPI_Open()3.1.2 R_SCI_SPI_Read()3.1.3  R_SCI_SPI_Write()3.2应用函数接口3.......
  • Python+pytest+jenkins 多插件 pdf电子书目录
    第1章pytest入门11.1资源获取 41.2运行Pytest 51.3运行单个测试用例 101.4使用命令行选项 10--collect-only选项 11-k选项 11-m选项 12-x选项 13--maxfail=num 15-s与--capture=method 16-lf(--lastfailed)选项 16--ff(--failed-first)选项 17......