本文想针对测试中一种很常见的测试场景,即参数化测试,持续聊聊对于测试的话题,并尝试将这几个测试框架串联起来,做一个横向的比对,加深了解。
1、什么是参数化测试?
对于一般测试来说,一个测试方法只须要运行一遍,而参数化测试对于一个测试方法,可能须要传入一系列参数,而后进行屡次测试。
比方,咱们要测试某个零碎的登录性能,就可能要别离传入不同的用户名与明码,进行测试:应用蕴含非法字符的用户名、应用未注册的用户名、应用超长的用户名、应用谬误的明码、应用正当的数据等等。
参数化测试是一种“数据驱动测试”(Data-Driven Test),在同一个办法上测试不同的参数,以笼罩所有可能的预期分支的后果。它的测试数据能够与测试行为拆散,被放入文件、数据库或者内部介质中,再由测试程序读取。
2、参数化测试的实现思路?
通常而言,一个测试方法就是一个最小的测试单元,其性能应该尽量地原子化和单一化。
先来看看两种实现参数化测试的思路:一种是写一个测试方法,在其外部对所有测试参数进行遍历;另一种是在测试方法之外写遍历参数的逻辑,而后顺次调用该测试方法。
这两种思路都能达到测试目标,在简略业务中,没有故障。然而,实际上它们都只有一个测试单元,在统计测试用例数状况,或者生成测试报告的时候,并不乐观。可扩展性也是个问题。
那么,现有的测试框架是如何解决这个问题的呢?
它们都借助了装璜器,次要的思路是:利用原测试方法(例如 test()),来生成多个新的测试方法(例如 test1()、test2()……),并将参数顺次赋值给它们。
因为测试框架们通常把一个测试单元统计为一个“test”,所以这种“由毕生多”的思路相比后面的两种思路,在统计测试后果时,就具备很大的劣势。
3、参数化测试的应用办法?
Python 规范库中的 unittest 本身不反对参数化测试,为了解决这个问题,有人专门开发了两个库:一个是 ddt,一个是 parameterize。
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 编程学习圈,发送“J”即可收费获取,每日干货分享