Python 中如何实现参数化测试的方法示例


Posted in Python onDecember 10, 2019

之前,我曾转过一个单元测试框架系列的文章,里面介绍了 unittest、nose/nose2 与 pytest 这三个最受人欢迎的 Python 测试框架。

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

1、什么是参数化测试?

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

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

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

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

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

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

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

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

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

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

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

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

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

import unittest
from ddt import ddt,data,unpack

@ddt
class 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) ... ok
test_values_2___1__0_ (__main__.MyTest) ... FAIL
test_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.001s

FAILED (failures=1)

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

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

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

import unittest
from parameterized import parameterized

class 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) ... ok
test_values_1 (__main__.MyTest) ... FAIL
test_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.000s

FAILED (failures=1)

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

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

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

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

import unittest
from nose2.tools import params

@params(1, 2, 3)
def test_nums(num):
  assert num < 4

class 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.1
rootdir: C:\Users\pythoncat\PycharmProjects\study collected 3 items

testparam.py .F
testparam.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 > 0

testparam.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 > 0

testparam.py:6: AssertionError
===================== 1 failed, 2 passed in 0.08s =====================
Process finished with exit code 0

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

4、最后小结

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

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python中readline判断文件读取结束的方法
Nov 08 Python
python遍历目录的方法小结
Apr 28 Python
python 用下标截取字符串的实例
Dec 25 Python
pyqt5利用pyqtDesigner实现登录界面
Mar 28 Python
Python用字典构建多级菜单功能
Jul 11 Python
django rest framework vue 实现用户登录详解
Jul 29 Python
Django获取该数据的上一条和下一条方法
Aug 12 Python
Python Multiprocessing多进程 使用tqdm显示进度条的实现
Aug 13 Python
Python使用tkinter模块实现推箱子游戏
Oct 08 Python
Python面向对象编程基础实例分析
Jan 17 Python
selenium携带cookies模拟登陆CSDN的实现
Jan 19 Python
利用Python多线程实现图片下载器
Mar 25 Python
利用python读取YUV文件 转RGB 8bit/10bit通用
Dec 09 #Python
YUV转为jpg图像的实现
Dec 09 #Python
Pandas+Matplotlib 箱式图异常值分析示例
Dec 09 #Python
Python箱型图处理离群点的例子
Dec 09 #Python
Python实现非正太分布的异常值检测方式
Dec 09 #Python
python 实现检验33品种数据是否是正态分布
Dec 09 #Python
Python远程开发环境部署与调试过程图解
Dec 09 #Python
You might like
删除及到期域名的查看(抢域名必备哦)
2008/05/14 PHP
php 远程图片保存到本地的函数类
2008/12/08 PHP
flash用php连接数据库的代码
2011/04/21 PHP
浅谈Laravel中的一个后期静态绑定
2017/08/11 PHP
PHP实现的简单对称加密与解密方法实例小结
2017/08/28 PHP
Date对象格式化函数代码
2010/07/17 Javascript
在次封装easyui-Dialog插件实现代码
2010/11/14 Javascript
基于jQuery的图片左右无缝滚动插件
2012/05/23 Javascript
使用js操作cookie的一点小收获分享
2013/09/03 Javascript
JS基于Mootools实现的个性菜单效果代码
2015/10/21 Javascript
向JavaScript的数组中添加元素的方法小结
2015/10/24 Javascript
详解用webpack2.0构建vue2.0超详细精简版
2017/04/05 Javascript
Mac中安装nvm的教程分享
2017/12/11 Javascript
深入解析koa之中间件流程控制
2019/06/17 Javascript
layui将table转化表单显示的方法(即table.render转为表单展示)
2019/09/24 Javascript
node.js中事件触发器events的使用方法实例分析
2019/11/23 Javascript
解决小程序无法触发SESSION问题
2020/02/03 Javascript
Python正则表达式介绍
2012/08/06 Python
Python编码类型转换方法详解
2016/07/01 Python
用Python将IP地址在整型和字符串之间轻松转换
2017/03/22 Python
python输入错误密码用户锁定实现方法
2017/11/27 Python
cmd运行python文件时对结果进行保存的方法
2018/05/16 Python
python统计字母、空格、数字等字符个数的实例
2018/06/29 Python
python之消除前缀重命名的方法
2018/10/21 Python
Python第三方库face_recognition在windows上的安装过程
2019/05/03 Python
Python循环结构的应用场景详解
2019/07/11 Python
python中从for循环延申到推导式的具体使用
2019/11/29 Python
Python Tricks 使用 pywinrm 远程控制 Windows 主机的方法
2020/07/21 Python
python利用paramiko实现交换机巡检的示例
2020/09/22 Python
python实现跨年表白神器--你值得拥有
2021/01/04 Python
Calzedonia美国官网:意大利风格袜子、打底裤和沙滩装
2018/07/19 全球购物
银行会计职员个人的自我评价
2013/09/29 职场文书
《夜晚的实验》教学反思
2014/02/19 职场文书
2014年为民办实事工作总结
2014/12/20 职场文书
环保建议书作文500字
2015/09/14 职场文书
80后创业总结的9条职场用人思想,记得收藏
2019/08/13 职场文书