之前,我曾转过一个单元测试框架系列的文章,里面介绍了 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 中如何实现参数化测试的方法示例相关推荐

  1. python 二重积分_python中求二维积分的方法

    python中一般求解微积分可以使符号积分求出解析解,使用数值积分求出数值解.在计算机的处理当中,数值解往往更有意义.本文介绍python中利用数值积分例程和微分方程求解器scipy.integrat ...

  2. python子类如何调用父类方法_python中子类调用父类函数的方法示例

    前言 本文主要给大家介绍了关于python子类调用父类函数的相关内容,Python中子类中的__init__()函数会覆盖父类的函数,一些情况往往需要在子类里调用父类函数.下面话不多说了,来一起看看详 ...

  3. python获取excel某一列-Python从Excel中读取日期一列的方法

    如下所示: import xlrd import datetime file=u"伏特加.xls"#注意读中文文件名稍微处理一下 data=xlrd.open_workbook(f ...

  4. python怎么读取excel某一列-Python从Excel中读取日期一列的方法

    如下所示: import xlrd import datetime file=u"伏特加.xls"#注意读中文文件名稍微处理一下 data=xlrd.open_workbook(f ...

  5. Python 获得pdf中的文字、图片文字方法

    Python 获得pdf中的文字.图片文字方法 下载word版文件 OCR,全称Optical character recognition,中文译名叫做光学文字识别.它把图像中的字符,转换为机器编码的 ...

  6. python统计单词频率、存放在字典中_Python3实现统计单词表中每个字母出现频率的方法示例...

    本文实例讲述了Python3实现统计单词表中每个字母出现频率的方法.分享给大家供大家参考,具体如下: 作为python字典与数组概念的运用,统计字母表中每个字母出现的频率,作为练习再合适不过. 解决问 ...

  7. PDFPlumber使用入门+python实现PDF中表格转化为Excel的方法

    PDFPlumber使用入门:https://blog.csdn.net/weixin_48629601/article/details/107224376 python实现PDF中表格转化为Exce ...

  8. python中出栈的方法,python中栈的原理及实现方法示例

    python中栈的原理及实现方法示例 本文实例讲述了python中栈的原理及实现方法.分享给大家供大家参考,具体如下: 栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素.访问元素.删除元 ...

  9. TIA博途中使用AT指令实现双字中高低字转换的具体方法示例

    TIA博途中使用AT指令实现双字中高低字转换的具体方法示例 西门子PLC采用的是大端存储,有些PLC品牌或上位机采用的小端存储,那么在通信时为了获得正确的数据,我们需要将一个双字DWORD中的高低字进 ...

最新文章

  1. matlab模块 python,Matlab 和Python结合使用
  2. python与excel的应用-python怎样在excel中应用?
  3. Oracle数据库常用的脚本命令(一)
  4. Delphi_04_Delphi_Object_Pascal_基本语法_02
  5. Android系统,动态找出一个包下所有的类
  6. Android 系统(196)---Android 属性动画
  7. Java中的ConcurrentHashMap
  8. pcie转sata3硬盘不启动_XPS 笔记本: 排除对 BIOS 默认设置的更改导致无法开机自检/无引导/硬盘或未检测到 SDD 问题...
  9. Bluecoat代理设备维护手册
  10. 安装Ubuntu虚拟机
  11. 神经网络架构搜索(NAS)综述
  12. 17.Future 介绍与主要用法
  13. 【LeNet-MNIST】
  14. oracle数据库进入控制台,oracle web浏览器无法打开控制台的解决办法
  15. 按键精灵 html转txt文件,按键精灵怎么把变量输出写入TXT文本
  16. Poco c ++ 库安装
  17. 【整理】PJSIP开源库详解
  18. Echarts柱状图实现点击事件
  19. 云码课堂的Java帮到了我,更感谢我自己
  20. 中兴视觉大数据投资70亿元在武汉建设中兴智能光谷产业基地

热门文章

  1. 自定义LinkedList
  2. 洛谷P2863 [USACO06JAN]牛的舞会The Cow Prom
  3. Java中方法参数的传递
  4. Android RecyclerView和ScrollView嵌套使用
  5. 结对编程-黄金点游戏
  6. ajax url参数中文乱码解决
  7. jQuery EasyUI datagrid本地分页
  8. Manager使用介绍
  9. wcf http 返回图片
  10. 一文教你json字符串与JavaBean对象如何相互转换