接口自动化测试中,往往测试的接口参数可能较多且测试场景复杂,比如测试参数必填、参数值非法,用例层如果直接定义接口的参数,存在大量重用数据且如果接口发生变更(删除一个参数、修改一个参数名)需要在用例层修改很多地方
为解决这个问题,肯定是要将接口定义和用例层分离处理。
具体思路如下:
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需要替换。
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