接口自动化测试用例层对接口参数的传递(二)


接口自动化测试中,往往测试的接口参数可能较多且测试场景复杂,比如测试参数必填、参数值非法,用例层如果直接定义接口的参数,存在大量重用数据且如果接口发生变更(删除一个参数、修改一个参数名)需要在用例层修改很多地方

为解决这个问题,肯定是要将接口定义和用例层分离处理

具体思路如下:

1.按模块**将接口定义在对应的yaml文件中,参数使用${parmaname}表示**(方便使用string.safe_substitute()方法进行参数的替换)
*substitute的解释可以查看这篇文章《python-字符串的模板替换》*
2.在baseapi这个父类的__init__方法中读取接口yaml的内容
3.业务api类继承baseapi,在具体接口方法中根据关键字拿到接口定义,根据case层传过来的参数做参数替换,替换后的内容去调用requestUtil中封装的接口处理方法
4.requestUtil的接口处理方法将接口参数没有被替换的${param}进行删除,返回给业务api
5.业务api调用requestsUtil封装的api方法,去发起请求

get接口按这个思路实现很简单,但是post、put接口因为其请求body体是json接口,处理起来稍微复杂一些

样例代码如下:
首先从简单的get接口开始:GET /v1/domains/ d o m a i n i d / p o l i c y 这个接口只有一个路径参数 {domain_id}/policy 这个接口只有一个路径参数 domainid/policy这个接口只有一个路径参数{domain_id}需要替换,headers中有一个$authtoken需要替换。

  1. 在yaml中这样定义接口:
show_domain_policy:
  method: get
  url: /v1/domains/${domain_id}/policy
  headers:
      Content-Type: application/json;charset=utf8
      authToken: $authtoken

2.在baseapi的init中定义从yaml文件中读取该业务api对应文件的所有api定义,会赋值到self.apiconfig变量

class BaseApi():
    __envdir = DIR_NAME + "/conf/env.yml"
    env =  yamlUtil(__envdir).readyaml()
    def __init__(self,serviceyaml):
        self.baseurl = self.env['protocl']+'://'+self.env['envConfig'][self.env['default']] ##从env文件读取参数拼接调用域名
        apidir  = os.path.abspath(os.path.realpath(__file__) + os.path.sep +'../')+'/apiyaml/'+serviceyaml
        self.apiconfig =yamlUtil(apidir).readyaml()
        self.req  = requestsUtil()
        self.logger = GetLogger().get_logger()
        pass
    def api(self,kws):
        url = kws['url']
        kws['url']=self.baseurl+url
        response = requests.request(**kws)
        self.logger.info('url= {},response = {},{} '.format(url,response.status_code,response.text))
        return response

3.业务api中定义业务方法从apiconfig中拿到具体接口定义

class SecurityApi(BaseApi):
    def __init__(self):
        BaseApi.__init__(self, 'SecurityApi.yaml')
    def show_domain_policy(self,**params):
        apiconfig = self.apiconfig['show_domain_policy'] #注意apiconfig[]中括号的键值要与定义的yaml中的key一样
        parms = self.req.replaceParams(apiconfig, **params)
        response = self.api(parms)
        return response

4.业务api带着参数,和接口配置调用requestsUtil中的replaceParams方法,上面代码中的parms = self.req.replaceParams(apiconfig, **params)
下面代码是requestUtil封装的函数,对参数进行替换并删除多余参数

class requestsUtil:
    logger = GetLogger.get_logger()

    def replaceParams(self,apiconfig,**params):
        strconfig = json.dumps(apiconfig)
        params = json.loads(Template(strconfig).safe_substitute(**params))
        if params.keys().__contains__('params'):
            params['params'] = self.deleteParams(params['params'])
            params['params'] = self.str2bool(params['params'])
        if params.keys().__contains__('headers'):
            params['headers'] = self.deleteParams(params['headers'])
        self.logger.info('request params = {}'.format(params))
        return params
    def deleteParams(self,apiparams):
        deletekey = []
        for key in apiparams:
            if str(apiparams[key]).find('$')!= -1:
                deletekey.append(key)
        for key in deletekey:
            apiparams.pop(key)
        return apiparams

json.loads(Template(strconfig).safe_substitute(**params))实现参数的替换,deleteParams实现删除多余没有被替换的参数,所以yaml中将接口支持的所有参数都要写上
替换删除完后将处理好的参数返回给业务api类
5.业务api类拿到处理好的参数进行业务调用

 parms = self.req.replaceParams(apiconfig, **params)
        response = self.api(parms)

6.用例层怎么传参?

 pwdpolicyresponse = self.securityPolicyapi.show_domain_policy(authtoken=g_secuadmintoken,
                                                                                   domain_id=g_secudomainid)

注意参数中的authtoken、domainid要与yaml中${}中的参数名一致,才能成功替换,如果这样调用

 pwdpolicyresponse = self.securityPolicyapi.show_domain_policy(domain_id=g_secudomainid)

没有传authtoken ,那么requestsUtil会将headers中的authToken删除,相当于验证不传递token参数调用接口的情况

接下来看较为复杂的带有jsonbody体的接口,这种post接口因为json体灵活度较高,且层层嵌套
如获取token的一个接口:

json1
{
 "auth": {
  "identity": {
   "methods": [
    "password"
   ],
   "password": {
    "user": {
     "domain": {
      "name": "aaa"
     },
     "name": "aaa",
     "password": "aaapassword"
    }
   }
  }
 }
}
json2:

{
 "auth": {
  "identity": {
   "methods": [
    "password"
   ],
   "password": {
    "user": {
     "domain": {
      "id": "aaadomainid"
     },
     "name": "aaa",
     "password": "aaapassword"
    }
   }
  }
 }
}

如果json中的参数被替换后出现这种情况:json嵌套中有未被替换的变量需要删除,但是删除是会将“domain”这整个字段删除,这种嵌套的情况最好将这部分json单独在业务api中构造

"scope":{
      "domain":{
          "id": ${id},
          "name": "aaa"
      }
  }
createUserTokenByPassword:
  method: post
  url: /v3/auth/tokens
  headers:
    Content-Type: application/json;charset=utf8
  json:
      auth:
        identity: 
          methods: ["password"]
          password: 
            user: 
              name: $username
              password: $userpwd
              domain: 
                name: $domainname
        scope: $scope
       
class TokenApi(BaseApi):
    def __init__(self):
        BaseApi.__init__(self,'tokenapi.yaml')
    def func_pwdtoken(self,**params):
        #根据参数构造获取tokenbody体的scope
        scopebody = {}
        if params.keys().__contains__('scopedomainname'):
            scopebody={'domain':{"name":params['scopedomainname']}}
            params.pop('scopedomainname')
        if params.keys().__contains__('scopedomainid'):
            scopebody={'domain':{"id":params['scopedomainid']}}
            params.pop('scopedomainid')
        if params.keys().__contains__('scopeprojectid'):
            scopebody={'project':{"id":params['scopeprojectid']}}
            params.pop('scopeprojectid')
        if params.keys().__contains__('scopeprojectname'):
            scopebody={'project':{"name":params['scopeprojectname']}}
            params.pop('scopeprojectname')
        params['scope']=scopebody
        return params
    def createUserTokenByPassword(self,**params):
        apiconfig = self.apiconfig['createUserTokenByPassword']
        params = self.func_pwdtoken(**params)
        params = self.req.replaceParams(apiconfig,**params)
        response = self.api(params)
        return response

你可能感兴趣的:(自动化测试,自动化,python)