24

自动化用例开发过程中的常见技巧:代理模式

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA4NTYwOTE3MQ%3D%3D&%3Bmid=2452533078&%3Bidx=1&%3Bsn=1ab3918048ecb8867cb683054872d497&%3Bchksm=880f50eabf78d9fce89c30e3298370aa1fb51aefb44dfdda748323f4a57c92bf10de70dbf9e7&%3Btoken=2011056306&%3Blang=zh_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
uyIvMvr.jpg!web

在上一次讲连接复用的时候,我实现了一个类用于接管 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

代理模式应该是一种比较容易理解的设计模式,你可以把它类比成服务部署中的 nginxapache http 这类服务,它不暴露原始的请求资源地址(对象),而是让nginx(proxy)来接管client(调用方)的所有请求,具备了通过nginx(proxy)植入一些额外的能力来实现对原始资源的扩展、控制等。

a2aeUja.png!web

这种模式的调用时序可以看下图:

2YjuMbm.png!web

我个人认为代理模式存在几点优势:

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

  • 代理模式可以控制对真实对象的使用权限

  • 代理接管了对真实对象的调用,实现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看上去并没有太多实际作用,于是我稍微扩展下,让其能够实现 beforeafter 这样的事件钩子:

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)的特性?感兴趣的童鞋可以留言告知

QBfAJ3Z.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK