Python接口测试实战5 - 面向服务封装

上一篇我们封装了def list_issues()接口,在这一节正式开讲前,我们以同样的方法封装另外个接口:

# https://www.redmine.org/projects/redmine/wiki/Rest_Issues#Listing-issues
def list_issues(api_key, **kwargs):
  """按条件查询issue"""
  return requests.get("http://redmine.xuh.me/issues.json", params=kwargs, headers={"X-Redmine-API-Key": api_key}).text


# https://www.redmine.org/projects/redmine/wiki/Rest_Issues#Showing-an-issue
def get_issue(api_key, issue_id, **kwargs):
  """根据issue_id获取issue详情"""
  return requests.get("http://redmine.xuh.me/issues/{}.json".format(issue_id), params=kwargs, headers={"X-Redmine-API-Key": api_key}).text

可以看到,封装的两个函数有很多问题:

  • 服务的入口地址一致,均为 http://redmine.xuh.me
  • api_key与接口逻辑无关,但每次封装都得带入
  • HTTP连接并未复用:直接调用了requests.request,导致每次接口调用都要进行TCP三次握手建链,性能不高

面向服务封装

基于上面的问题,以及考虑到测试人员会额外面对的另以个问题:同一个服务在不同环境(开发/测试/预发布等)的访问入口有区别,我这里提出了一个封装原则:面向服务封装

  • 服务可以狭义的理解为一个服务实例,也可以是共享一个访问入口的服务实例群
  • 服务以Class的形态存在,接口的形式则为类内的方法
  • Class的实现上应当复用TCP连接,即大多数情况下,应当使用同一requests.Session对象
  • 用户鉴权、数字签名等非接口语义的行为应当尽可能抽取出来
  • 封装方法的函数签名应当尽可能逼近终端用户的使用理解

在理解了以上原则后,看下我会怎么封装redmine的接口:

class RedmineClient:

    def __init__(self, base_url, api_key=None):
        self.base_url = base_url  # 服务入口地址,不同环境入口地址不同
        self.api_key = api_key
        self.session = requests.Session()

    def list_issue(self, **kwargs):
        return self.session.get("{}/issues.json".format(self.base_url), params=kwargs, headers={"X-Redmine-API-Key": self.api_key}).text

    def get_issue(self, issue_id, **kwargs):
        return self.session.get("{}/issues/{}.json".format(self.base_url, issue_id), params=kwargs, headers={"X-Redmine-API-key": self.api_key}).text

然后实例化这个Client: client = RedmineClient("http://redmine.xuh.me", "490592ea4e46348df29828edd597acbfdc5ebb4a"),调用下上面两个接口看下:

如果你对前后两个版本的封装进行时间打点的话,你会发现维护一个Session对象来调用接口的话,除了第一次稍慢(TCP建链),之后的响应时间会节省不少,远远优于直接使用requests.request

本节到此就差不多结束了,RedmineClass内部的实现其实还有很多优化的余地,之后会陆续讲到。下一节会重点讲如何到响应报文进行提取、断言,这里面有一个极为重要的知识,而大多数接口自动化工程师却浑然不知