Python中参数化测试的实现思路解析

时间:2025-11-01  作者:Diven  阅读:0

本文想针对测试中很常见的测试场景,即参数化测试,继续聊聊关于测试的话题,并尝试将这几个测试框架串联起来,做一个横向的比对,加深理解。

Python中参数化测试的实现思路解析

1、什么是参数化测试?

对于普通测试来说,一个测试方法只需要运行一遍,而参数化测试对于一个测试方法,可能需要传入一系列参数,然后进行多次测试。

比如,我们要测试某个系统的登录功能,就可能要分别传入不同的用户名与密码,进行测试:使用包含非法字符的用户名、使用未注册的用户名、使用超长的用户名、使用错误的密码、使用合理的数据等等。

参数化测试是“数据驱动测试”(Data-Driven Test),在同一个方法上测试不同的参数,以覆盖所有可能的预期分支的结果。测试数据可以与测试行为分离,被放入文件、数据库或者外部介质中,再由测试程序读取。

2、参数化测试的实现思路?

通常而言,一个测试方法就是一个最小的测试单元,其功能应该尽量地原子化和单一化。

先来看看两种实现参数化测试的思路:是写一个测试方法,在其内部对所有测试参数进行遍历;另是在测试方法之外写遍历参数的逻辑,然后依次调用该测试方法。

这两种思路都能达到测试目的,在简单业务中,没有毛病。实际上都只有一个测试单元,在统计测试用例数情况,或者生成测试报告的时候,并不乐观。可扩展性也是个问题。

那么,现有的测试框架是如何解决这个问题的呢?

都借助了装饰器,主要的思路是:利用原测试方法(例如 test()),来生成多个新的测试方法(例如 test1()、test2()……),并将参数依次赋值给。

由于测试框架们通常把一个测试单元统计为一个“test”,所以这种“由一生多”的思路相比前面的两种思路,在统计测试结果时,就具有很大的优势。

3、参数化测试的使用方法?

Python 标准库中的unittest自身不支持参数化测试,为了解决这个问题,有人专门开发了两个库:一个是ddt,一个是parameterize。

ddt 正好是“Data-Driven Tests”(数据驱动测试)的缩写。典型用法:

import unittestfrom ddt import ddt,data,unpack@ddtclass MyTest(unittest.TestCase): @data((3, 1), (-1, 0), (1.2, 1.0)) @unpack def test_values(self, first, second): self.assertTrue(first > second)unittest.main(verbosity=2)

运行的结果如下:

test_values_1__3__1_ (__main__.MyTest) ... oktest_values_2___1__0_ (__main__.MyTest) ... FAILtest_values_3__1_2__1_0_ (__main__.MyTest) ... ok==================================================FAIL: test_values_2___1__0_ (__main__.MyTest)--------------------------------------------------Traceback (most recent call last): File "C:/Python36/lib/site-packages/ddt.py", line 145, in wrapper return func(self, *args, **kwargs) File "C:/Users/pythoncat/PycharmProjects/study/testparam.py", line 9, in test_values self.assertTrue(first > second)AssertionError: False is not true----------------------------------------------Ran 3 tests in 0.001sFAILED (failures=1)

结果显示有 3 个 tests,并详细展示了运行状态以及断言失败的信息。

需要注意的是,这 3 个 test 分别有一个名字,名字中还携带了其参数的信息,而原来的 test_values 方法则不见了,已经被一拆为三。

在上述例子中,ddt 库使用了三个装饰器(@ddt、@data、@unpack),实在是很丑陋。下面看看相对更好用的 parameterized 库:

import unittestfrom parameterized import parameterizedclass MyTest(unittest.TestCase): @parameterized.expand([(3,1), (-1,0), (1.5,1.0)]) def test_values(self, first, second): self.assertTrue(first > second)unittest.main(verbosity=2) 

测试结果如下:

test_values_0 (__main__.MyTest) ... oktest_values_1 (__main__.MyTest) ... FAILtest_values_2 (__main__.MyTest) ... ok=========================================FAIL: test_values_1 (__main__.MyTest)-----------------------------------------Traceback (most recent call last): File "C:/Python36/lib/site-packages/parameterized/parameterized.py", line 518, in standalone_func return func(*(a + p.args), **p.kwargs) File "C:/Users/pythoncat/PycharmProjects/study/testparam.py", line 7, in test_values self.assertTrue(first > second)AssertionError: False is not true----------------------------------------Ran 3 tests in 0.000sFAILED (failures=1)

这个库只用了一个装饰器 @parameterized.expand,写法上可就清爽多了。

同样提醒下,原来的测试方法已经消失了,取而代之的是三个新的测试方法,只是新方法的命名规则与 ddt 的例子不同罢了。

介绍完 unittest,接着看已经死翘翘了的nose以及新生的nose2。nose 系框架是带了插件(plugins)的 unittest,以上的用法是相通的。

另外,nose2 中还提供了自带的参数化实现:

import unittestfrom nose2.tools import params@params(1, 2, 3)def test_nums(num): assert num < 4class Test(unittest.TestCase): @params((1, 2), (2, 3), (4, 5)) def test_less_than(self, a, b): assert a < b

再来看下 pytest 框架,这样实现参数化测试:

import pytest@pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)])def test_values(first, second): assert(first > second)

测试结果如下:

==================== test session starts ====================platform win32 -- Python 3.6.1, pytest-5.3.1, py-1.8.0, pluggy-0.13.1rootdir: C:/Users/pythoncat/PycharmProjects/study collected 3 itemstestparam.py .Ftestparam.py:3 (test_values[-1-0])first = -1, second = 0 @pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)]) def test_values(first, second):> assert(first > second)E assert -1 > 0testparam.py:6: AssertionError. [100%]========================= FAILURES ==========================_________________________ test_values[-1-0] _________________________first = -1, second = 0 @pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)]) def test_values(first, second):> assert(first > second)E assert -1 > 0testparam.py:6: AssertionError===================== 1 failed, 2 passed in 0.08s =====================Process finished with exit code 0

依然要提醒大伙注意,pytest 也做到了由一变三,然而我们却看不到有新命名的方法的信息。这是否意味着并没有产生新的测试方法呢?或者仅仅是把新方法的信息隐藏起来了?

4、最后小结

上文中介绍了参数化测试的概念、实现思路,以及在三个主流的 Python 测试框架中的使用方法。我只用了最简单的例子,为的是快速科普(言多必失)。

但是,这个话题其实还没有结束。对于我们提到的几个能实现参数化的库,抛去写法上大同小异的区别,在具体代码层面上,又会有什么样的差异呢?

具体来说,是如何做到把一个方法变成多个方法,并且将每个方法与相应的参数绑定起来的呢?在实现中,需要解决哪些棘手的问题?

在分析一些源码的时候,我发现这个话题还挺有意思,所以准备另外写一篇文章。那么,本文就到此为止了,谢谢阅读。

作者简介: 豌豆花下猫,生于广东毕业于武大,现为苏漂程序员,有一些极客思维,也有一些人文情怀,有一些温度,还有一些态度。

编辑:hfy
猜您喜欢

现代电子技术中,VSON8_3X3MM_EP作为一种高效的封装形式,受到了广泛的关注。它以其小巧的尺寸和优越的性能,成为许多电子产品设计中的理想选择。本文将详细...
2025-02-24 16:11:26

芯森CM6A霍尔效应电流传感器是一种能够通过测量磁场变化来检测电流的装置。它利用了霍尔效应的原理,即当电流通过材料时会产生磁场,这个磁场又会引起电荷偏转,从而产...
2023-09-15 08:32:00

电容器是电子电路中常见的元件。充电过程非常重要。了解这个过程,有助于我们更好地使用电容器。本文将对此进行分析。电容器的基本概念电容器是储存电能的装置。由两个导体...
2025-04-12 21:30:03

一、温度传感器热电偶的应用原理温度传感器热电偶是工业上最常用的温度检测元件之一。其优点是:①测量精度高。因温度传感器热电偶直接与被测对象接触,不受中间介质的...
2023-09-13 11:27:00

当今快速发展的科技时代,各种电子元件和连接器的需求日益增加。其中,TERMINAL_14.4X10.1MM_TM作为重要的连接器组件,因其独特的尺寸和功能而受到...
2025-04-26 20:00:39

电源监控主机是保障电力系统安全稳定运行的重要设备,正确安装是确保其发挥功效的关键。本指南将为您详细介绍电源监控主机的安装步骤,即使您不是专业电工,也能轻松掌握。...
2024-10-26 00:00:00

贴片排阻作为电子元件中的重要组成部分,应用于各种电路设计中。Walsin(华新)作为知名的贴片排阻品牌,其产品以高品质和稳定性能赢得了众多工程师的青睐。那么,在...
2024-09-26 06:49:32

现代生活中,吹风机已成为我们日常护理的重要工具。许多人可能并不知道,吹风机内部的电路设计对于其使用性能和安全性有着重要影响。本文将探讨去掉吹风机开关中的二极管这...
2025-04-08 06:01:39

现代电子设备中,二极管的应用非常,尤其是贴片二极管由于其小巧的体积和优良的性能,成为了电子产品设计中的重要组成部分。BAS16贴片二极管是常用的二极管型号,应用...
2025-04-07 19:31:06

MOS管(金属氧化物半导体场效应晶体管)应用于各种电子电路中,尤其是在开关电源和信号放大器中。为了确保电路的正常工作,进行电流检测是非常重要的,而电流检测电阻(...
2025-04-15 02:31:09