Python 中如何实现参数化测试?
Python 中如何实现参数化测试?
之前,我曾转过一个单元测试框架系列的文章,里面介绍了 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 中如何实现参数化测试?的更多相关文章
- <自动化测试>之<使用unittest Python测试框架进行参数化测试>
最近在看视频时,虫师简单提到了简化自动化测试脚本用例中的代码量,而python中本身的参数化方法用来测试很糟糕,他在实际操作中使用了parameterized参数化... 有兴趣就查了下使用的方法,来 ...
- Google C++单元测试框架GoogleTest---值参数化测试
值参数化测试允许您使用不同的参数测试代码,而无需编写同一测试的多个副本. 假设您为代码编写测试,然后意识到您的代码受到布尔参数的影响. TEST(MyCodeTest, TestFoo) { // A ...
- python中的buildin函数详解(第一篇)
这会是很长的一个帖子,因为我打算从python最基础的东西开始,尝试去完全的掌握它,buildin中有一些常用的函数比如 abs, open, setattr, getattr, 大家都很了解他们的用 ...
- 可能是 Python 中最火的第三方开源测试框架 pytest
作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...
- 利用Python中的mock库对Python代码进行模拟测试
这篇文章主要介绍了利用Python中的mock库对Python代码进行模拟测试,mock库自从Python3.3依赖成为了Python的内置库,本文也等于介绍了该库的用法,需要的朋友可以参考下 ...
- Python中生成器和迭代器的区别(代码在Python3.5下测试):
https://blog.csdn.net/u014745194/article/details/70176117 Python中生成器和迭代器的区别(代码在Python3.5下测试):Num01–& ...
- 【转】利用Python中的mock库对Python代码进行模拟测试
出处 https://www.toptal.com/python/an-introduction-to-mocking-in-python http://www.oschina.net/transla ...
- Python中的测试工具
当我们在写程序的时候,我们需要通过测试来验证程序是否出错或者存在问题,但是,编写大量的测试来确保程序的每个细节都没问题会显得很繁琐.在Python中,我们可以借助一些标准模块来帮助我们自动完成测试 ...
- Junit5中实现参数化测试
从Junit5开始,对参数化测试支持进行了大幅度的改进和提升.下面我们就一起来详细看看Junit5参数化测试的方法. 部署和依赖 和Junit4相比,Junit5框架更多在向测试平台演进.其核心组成也 ...
随机推荐
- 相关推导式-Python
列表.’字典等推导式 #利用zip()函数同时给多个变量赋值 a = [1,2,3,4,5] b = [4,5,6,7,8] c = [9,2,3,4,0] l = [[1,2],[3,4]] for ...
- JDBC向数据库中写数据
package MYSQK; import java.sql.*; /** * PreparedStatement 对象可以对sql语句进行预编译,预编译的信息会存在存储该对象中,当相同的sql语句再 ...
- Maven配置setting.xml详细说明
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...
- JavaScript中继承的实现方法--详解
最近看<JavaScript王者归来>中关于实现继承的方法,做了一些小总结: JavaScript中要实现继承,其实就是实现三层含义:1.子类的实例可以共享父类的方法:2.子类可以覆盖父类 ...
- 《吊打面试官》系列-Redis终章_凛冬将至、FPX_新王登基
你知道的越多,你不知道的越多 点赞再看,养成习惯 前言 Redis在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行360°的刁难.作为一个在互联 ...
- SpringBoot Web篇笔记(一)
摘要 文章是根据江南一点雨(松哥)的视频进行总结 江南一点雨博客 全局异常处理 通常情况下,我们都需要对自己定义的异常进行相应的处理.捕获指定的异常方式如下: @ControllerAdvice pu ...
- fatal: Authentication failed for 'http://git
git pull 出现 fatal: Authentication failed for 'http://git... git config --system --unset credential.h ...
- 开启docker远程访问
开启docker远程访问 进入到/lib/systemd/system/docker.service vim /lib/systemd/system/docker.service 找到ExecStar ...
- 013.Kubernetes二进制部署worker节点Nginx实现高可用
一 Nginx代理实现kube-apiserver高可用 1.1 Nginx实现高可用 基于 nginx 代理的 kube-apiserver 高可用方案. 控制节点的 kube-controller ...
- 删除TFS上的团队项目
Visual Studio 提供了一个工具 在X:\X\Microsoft Visual Studio X\Common7\IDE Visual Studio安装路径 下 TFSDeletepr ...