在做自动化测试时,我们通常有这样的需求,对某一个用例,希望可以根据我们提供的不同数据而运行多次,每次运行加载一条数据,但是运行的是同一个用例,仅仅是数据不同。 这个模式Java里TestNG有个概念叫DataProvider, python里也有,叫ddt。 上次我们介绍了用法,连接在此 -- Python数据驱动深入实践(一) ,今天来讲下如何自己实现一个ddt。
我们先来看一个简单例子:
def sum_data(x, y):
return x + y
if __name__ == "__main__":
assert sum_data(1, 2) == 3
assert sum_data(4, 5) ==8
正常来说, 要验证这个函数的实现正确与否,最基本的两个检查点一定是一个通过一个不通过。 上面代码可以看到我们写了两条用例,基本实现了我们的需求,但是感觉还有优化的空间,于是我们改成了这样
def sum_data(x, y):
return x + y
def test_sum_data(x, y, z):
assert sum_data(x, y) == z
if __name__ == "__main__":
test_sum_data(1, 2, 3)
test_sum_data(4, 5, 6)
看起来简单了一点,但是对于接口测试来说,一个函数可能验证的点有几十条,难道我们要copy-paste test_sum_data这个函数多次吗?
大家都知道, python里有装饰器这个概念,装饰器最大的特点就是接收一个函数称为参数,然后做一些”夹带私货“的操作后再返回这个函数。 如果你不是很了解装饰器的话,也没关系,你只要记住如下转换就可以了:
#假设你有一个函数叫func, 你有一个装饰器函数叫decorator, 那么如下代码
@decorator
def func():
pass
python解释器会解释成下面这样的语句:
func = decorator(func)
#如果你的装饰器decorator带参数怎么办?
@decorator(arg1, arg2)
def func():
pass
#相当于:
func = decorator(arg1,arg2)(func)
了解了上面,那我们继续看,我们应该怎么用装饰器来精简我们的函数:
def sum_data(x, y):
return x + y
def my_data(test_data):
def wraps(func):
def repl(x, y, z):
for i in test_data:
x, y, z = i
print('func starts')
func(x, y, z)
print('func ends')
return repl
return wraps
@my_data([(1, 2, 3), (4,5,6)])
def test_sum_data(x, y , z):
assert sum_data(x, y) == z
if __name__ == "__main__":
test_sum_data(1, 2, 4)
#运行一遍看, 结果如下:
Traceback (most recent call last):
File "88.py", line 22, in <module>
test_sum_data(1, 2, 4)
File "88.py", line 11, in repl
func(x, y, z)
File "88.py", line 18, in test_sum_data
assert sum_data(x, y) == z
AssertionError
func starts
func ends
func starts
可以看到函数第2此运行的时候失败了,报的错也是assert error。 你们发现没? 函数test_sum_data的自身的实参不起作用了, 变成从my_data提供了,那么我的函数就要相应的优化下
def sum_data(x, y):
return x + y
def my_data(test_data):
def wraps(func):
def repl(*args):
for i in test_data:
print('func starts')
x, y , z = i
func(x, y , z)
print('func ends')
return repl
return wraps
@my_data([(1, 2, 3), (4,5,9)])
def test_sum_data(x, y , z):
print("Your are verifing ({} + {}) == {}".format(x, y, z))
assert sum_data(x, y) == z
if __name__ == "__main__":
test_sum_data()
#这样我们在方法test_sum_data后面不加参数就可以了。
#注意:*args是可变参数的意思,
#它的实现实际上是个unpack,大家有兴趣可以自行了解下。
可是还有个问题,我想把这个数据驱动给我不同的函数使用怎么办?
def sum_data(x, y):
return x + y
def is_equal(x, y):
return x==y
def my_data(test_data):
def wraps(func):
def repl(*args, **kwargs):
for i in test_data:
print('func starts')
func(*i, **kwargs)
print('func ends')
return repl
return wraps
@my_data([(1, 2, 3), (4,5,9)])
def test_sum_data(x, y , z):
print("Your are verifing ({} + {}) == {}".format(x, y, z))
assert sum_data(x, y) == z
@my_data([(2,2), (1, 2)])
def test_is_equal(x, y):
print("Your are verifing {} == {}".format(x, y))
assert is_equal(x, y) ==1
if __name__ == "__main__":
test_sum_data()
test_is_equal()
#运行一下, O了。
就这样,我们一步一步实现了数据驱动的功能,实际上,如果你看过Python DataProvidor–ddt的源码,就发现,它实际上也是这么实现的。
本文的主要难点在装饰器上,而对于装饰器的理解,主要难点在闭包上,大家可以相应的搜索下,如有疑问,欢迎交流。
Hold On, 这就结束了吗? NO!
我们知道,自动化过程中我们通常会希望一个case在报告里有一个对应结果. 但对于我们实现的DataProvider,在测试报告里只会显示一条记录,太不利于我们调试了,那么,如何在测试报告里体现呢? 且听下回分解:)