装饰器的定义是给一个对象动态加载功能,就像打游戏时给队友上buff一样。一直以来,我对装饰器用的不多,经常会用别的方式搞定,虽然代码丑一点,但也能用。这次遇到一个特别适合装饰器的应用场景,就是执行单元测试时的环境配置。
我是用pytest做单元测试,测试入口都是一个个test_打头的函数。和unittest不一样,pytest中并没有setUp这个方法,虽然有fixture,但读人家的源码时也很少看到有人用,这次遇到问题发现,我靠,就是加个装饰器的事,可以把setUp和tearDown一起做了,何必多此一举。
应用场景用个demo举例,由于生产环境和测试环境的不同,在测试环境中初始化Demo会报错,比如下面这个模块。
importsys
classDemo():
def__init__(self):
fail()
deffail():
ifsys.argv[0].split('\\')[-1].find('test')>-1:
raiseEnvironmentError(__name__)
defsuccess():
print(__name__)
定义了一个Demo类,初始化时会调用fail函数,这个函数在pytest环境下使用时会raise一个EnvironmentError。解决方案就是在这个Demo被调用时将模块中的fail函数替换为success函数。两个单元测试用例如下。
importdemo
deftest_demo():
try:
demo.Demo()
exceptExceptionase:
assertisinstance(e,EnvironmentError)
@replace#替换环境的装饰器
deftest_replace_demo():
demo.Demo()
其中,replace就是替换环境用的装饰器。装饰器代码如下。
defreplace(fun):#定义装饰器,传入函数名
_=demo.fail#保存模块中的fail,以便后面恢复
demo.fail=demo.success#更改
definner():#闭包函数
fun()
demo.fail=_#恢复模块中fail函数
returninner()
用装饰器配置单元测试环境真是优雅无比,写完后顿时腰不酸背不痛了呢。。。
装饰器的难点之一就是闭包(closure),闭包这个翻译在中文里没有很形象的对应关系,这造成了理解障碍。这种例子挺多的,就像单例模式最广泛的用途并不是提供一个的“单例”,叫“超级全局宇宙唯一变量”比较好。协程的功能里一点‘协’的作用都没有,叫“想在什么时候挂起就在什么适合挂起程”比较好。这样一想,闭包是不是叫“跨作用域包”或“脚踏两条船包”或“闭合环境打包”比较好。
闭包的一个定义是这样的:
Closuresarefunctionsthatrefertoindependent(free)variables(variablesthatareusedlocally,butdefinedinanenclosingscope).Inotherwords,thesefunctions'remember'theenvironmentinwhichtheywerecreated.
闭包的关键能力之一是获取上级作用域,另一个关键点在于python中函数是一个对象,可以传来传去,比如作为返回值。这两点结合起来就可以将函数所处的环境打包传到目标区域。在上面一个例子中inner就是一个闭包函数,它在这里的作用就是将inner函数之前的那个区域,就是第二行和第三行。
下面还有个闭包的例子,可以看到在inner外定义了a,b,在inner中使用了a,在test_closure函数实例化后可以看看其__closure__方法中的对象,这个__closure__只包含了a,并没包含b,因为b没有在inner中使用,被垃圾回收了,而a保留了下来,而a就是由于闭包的特性保留下来的,可以用pdb来看看__closure__中是否保留了b。
deftest_closure():
a,b=1,2
definner():
print(a)
returninner
close=test_closure()
print(close.__closure__[0])#
另外,python中的闭包和javascript中的闭包是一个意思,可能需要实现的功能没js中那么多,但理解python闭包时可以参考js的教程。
以上内容为大家介绍了Python使用装饰器在执行单元测试时配置环境,希望对大家有所帮助,如果想要了解更多Python相关知识,请关注IT培训机构:千锋教育。