内容简介:在上一次讲连接复用的时候,我实现了一个类用于接管这其实是一个很典型的具体完整解释请参考维基百科词条:https://en.wikipedia.org/wiki/Proxy_pattern
在上一次讲连接复用的时候,我实现了一个类用于接管 pymysql.Connection
:
class MySQLConnectionProxy: def __init__(self, *args, **kwargs): self._conn = pymysql.Connect(*args, **kwargs) def __getattr__(self, item): return getattr(self._conn, item)
这其实是一个很典型的 Proxy Pattern :给某一个对象提供一个代理,并由代理对象控制对原对象的引用
具体完整解释请参考维基百科词条:https://en.wikipedia.org/wiki/Proxy_pattern
代理模式应该是一种比较容易理解的设计模式,你可以把它类比成服务部署中的 nginx
、 apache http
这类服务,它不暴露原始的请求资源地址(对象),而是让nginx(proxy)来接管client(调用方)的所有请求,具备了通过nginx(proxy)植入一些额外的能力来实现对原始资源的扩展、控制等。
这种模式的调用时序可以看下图:
我个人认为代理模式存在几点优势:
-
代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
-
代理模式可以控制对真实对象的使用权限
-
代理接管了对真实对象的调用,实现AOP(切面编程)的能力
实现一个Proxy类
在 Python 中因为有着 __getattr__
、 __setattr__
这样的magic method,所以实现一个通用的Proxy类是非常方便的事情:
class Proxy: def __init__(self, subject): self._subject = subject def __getattr__(self, item): return getattr(self._subject, item) def __setattr__(self, item, value): if item == "_subject": super().__setattr__(item, value) else: setattr(self._subject, item, value) class Student: def __init__(self, name, age: int): self.name = name self.age = age def info(self): return "name: {}\tage:{}".format(self.name, self.age)
mike = Proxy(Student("mike", 20)) print(mike.info()) mike.age = 24 print(mike.info()) print(type(mike)) name: mike age:20 name: mike age:24 <class '__main__.Proxy'>
可以看到上面十行代码就实现了通用的Proxy,不过这样的Proxy看上去并没有太多实际作用,于是我稍微扩展下,让其能够实现 before
、 after
这样的事件钩子:
class Proxy: def __init__(self, subject): self._subject = subject self._handlers = dict() def __getattr__(self, item): before = "before_{}".format(item) if before in self._handlers: self._handlers[before](item) ret = getattr(self._subject, item) after = "after_{}".format(item) if after in self._handlers: self._handlers[after](item) return ret def register(self, handler, method: str, scope: str): """注册任意函数的拦截器,实现对任意函数的after、before的钩子""" self._handlers["{}_{}".format(scope, method)] = handler def __setattr__(self, item, value): if item == "_subject" or item == "_handlers": super().__setattr__(item, value) else: setattr(self._subject, item, value) def print_before(item): print("before invoke {} method".format(item)) def print_after(item): print("after invoke {} method".format(item)) jack = Proxy(Student("jack", 16)) jack.register(print_before, "info", "before") jack.register(print_after, "info", "after") jack.info()
>>> before invoke info method >>> after invoke info method >>> 'name: jack\tage:16'
以上代码可能会对不太熟悉Python编程的自动化测试人员产生很大的困扰,不用太紧张,如果在你的项目要应用到代理模式,你可以针对要具体代理的对象进行具体的实现,不需要用到这么多magic method造成理解的障碍。
自动化测试用的应用
在上一次讲连接复用的末尾,我抛出了几个问题:
-
如果 mysql 连接被服务端主动关闭了怎么办?
-
因为是单例模式,如何防止有用例主动关闭mysql连接而影响其他用例?
然后我再抛出一个需求: 如何让框架自动记录mysql的查询记录,而不是手动去打日志?
以上三个问题其实在代理模式下都可以非常方便、优雅的来解决掉:
import pymysql class CursorProxy(Proxy): def execute(*args, **kwargs): # 这里可以实现日志记录 return getattr(self._subject, "execute")(*args, **kwargs) class MySQLConnectionProxy: def __init__(self, *args, **kwargs): self._conn = pymysql.Connect(*args, **kwargs) def __getattr__(self, item): if self._conn.open: # 这里可以检查是否被动关闭,然后实现重连 self._conn.connect() if item == "close": # 当用例要主动关闭时,无视该调用即可 return if item == "cursor": # 游标对象也需要代理 def _curor(*args, **kwargs): return CurorProxy(getattr(self._subject, item)(*args, **kwargs)) return _curor return getattr(self._conn, item)
结合这两节的内容来看,通过不太多的代码行数,在目前的自动化测试框架中已经实现了中间件client以下能力:
-
连接的复用
-
连接的自动重试、关闭保护
-
client请求内容的自动化日志记录
最后我抛出一个注意事项:在Python下使用代理模式后,能不能保留下原对象的上下文管理器(Context Manager)的特性?感兴趣的童鞋可以留言告知
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 自动化用例开发过程中的常见技巧:连接复用
- 自动化用例开发过程中的常见技巧:连接复用
- 干货丨实现UI自动化测试,这5个常见问题你应该知道!
- 自动化用例开发过程中的常见技巧:如何让用例支持多环境?
- 常见算法总结 - 链表篇
- 常见算法总结 - 排序篇
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。