context上下文,需要运用在接口执行里
参考jmeter后置处理器来设计,借用进程的内存空间来储存上下文
那么这个上下文变量放哪里呢?怎么才能做到每个用例都可以共享呢?
结合之前的代码,我们可以放在用例执的时候,没个接口执行之前。也就是common的perform_case里
admin CaseApiDef 里加两字段
per_proc = models.TextField(blank=True, null=True, verbose_name='前置处理')
post_proc = models.TextField(blank=True, null=True, verbose_name='后置处理')
定义好了要在内联表里展示
现在要用后置处理器取uuid的话用户需要写一下python脚本
#{case_ctx['uuid'] = parse(reslut['text'])['uuid']}
加上后置处理并传入case_ctx,顺便用日志记录下。这样用户才能去调用 case_ctx['uuid']
我们再抄下之前python表达式校验的代码改一下就可以用了,之后我们再考虑提取复用的问题。
common里重新写一个:
def eval_expression(input_, result, case_ctx): # 仅先实现后置处理 TODO
if not input_:
return
m = re.match(r"#\{.+\}", input_)
if not m:
raise Exception(f'内容格式不支持,请使用#{{}}包含[{input_}]')
exp = m.group()
if re.search(r"__.+__", input_): # __import__.os 等被过滤掉
raise Exception(f'python表达式包含非法字符或操作')
# 约定可提供的数据 result
local_params = {
'result': result,
're': re,
'parse': common.parse_json_like,
'case_ctx': case_ctx,
}
try:
# 执行eval
eval(exp[2:-1], {}, local_params) # eval执行后会返回一个布尔值,[2:-1]做个切片去掉用户输入的#{}
# 可以不要返回值 所以不用 if not eval_:
except Exception as e:
raise Exception(f'表达式执行失败,请先修正后再执行用例。参考【{e}】')
去运行用例,此时会报错
断点看下用户输入的我们取道了,但是没写入。上面代码最后一行加上,
trace_msg = traceback.format_exc() 抛出详细信息再看看
排除后发现我们在执行用户给的代码时出错了。。。
eval和exec
eval() 函数用来执行一个字符串表达式,并返回表达式的值。
exec 执行储存在字符串或文件中的Python语句,相比于 eval,exec可以执行更复杂的 Python 代码。 exec obj ; obj -- 要执行的表达式。
上面的代码eval的时候就保存了,改成exec就能正常执行了。我们接到异常后又再抛出异常,感觉不够优雅,干脆直接报错,我们把异常交给调用它的函数来处理
参数传递
以上执行后可以发现我们 case_ctx = {}里uuid值了,那用户要怎么用?我们想要这些字段都能用上
在common里再写个方法,通过getattr循环取 redis_key , auth_username……的值,没取到就继续跑,取到了就给他应该默认的方法,方法暂时没想到
def proc_apidef_params(item: CaseApiDef, case_ctx):
for attr in ['redis_key', 'auth_username', 'auth_password', 'bearer_token']:
val = getattr(item, attr)
if not val:
continue
setattr(item, attr, '??')
接下来写??位置的函数:
把用户输入的val校验里面包含#{}的排除掉,如果排除后里面是0就直接返回它本身并结束。
local_params是给用户调用的
re.sub()是把 #{}里的替换成 new_content ,从用户输入的val里找
new_content ()里matched 是前面 re.sub里r"#\{.+\}"匹配到的:<re.Match object; span=(0, 19), match="#{case_ctx['uuid']}"> 我们 让 s=matched.group() 就可以取全部组了,值就是 #{case_ctx['uuid']}
def proc_params_expression(val, case_ctx):
ms = re.findall(r"#\{.+\}", val)
if len(ms) == 0:
return val
local_params = {
'case_ctx': case_ctx,
're': re,
'parse': common.parse_json_like
}
def new_content(matched):
s = matched.group()
if re.search(r"__.+__", s): # __import__.os 等被过滤掉
raise Exception(f'python表达式包含非法字符或操作[{s}]')
return eval(s[2:-1], {}, local_params)
return re.sub(r"#\{.+\}", new_content, val)
以上就可以通过用户写的python脚本取到验证码了,再校验是不是数字
之后要写登录接口,我们想要把登录接口传入case_ctx里的uuid和code,需要用户在请求体里写
{"username":"admin",
"password":"wjxMh5UdGNAWp93UlFAvrOIGd7xUXYJdjULHVkuntNmRoFtf473yQ5RI+jV9WZ9ROWU0xShcQgZp+unqbWyjNQ==",
"code":"#{case_ctx['code']}",
"uuid":"#{case_ctx['uuid']}"}
运行下用例,现在我们的首要目的就是去把用户写的脚本替换成参数
请求体中的参数替换
经过分析发现在这个阶段的请求体是字符串,所以在这里处理是最合适的。
我们跳进 item.get_request_bodies() 里修改一下
def get_request_bodies(self, case_ctx=None):
result = {}
for p in self.request_bodies.all(): # type: CaseApiDefRequestBody
if self.api.body_type in ('raw-json', 'raw-xml', 'raw-text'):
# 让用户能用表达式获取上下文,如果传入case_ctx就执行表达式;没有就返回它本身
val = u.common.proc_params_expression(p.raw_value, case_ctx) if case_ctx else p.raw_value
return val
result.update({p.param_name: p.param_value}) # todo
return result
在用例里运行获取test_plt_case_apidef_requestbody 表的queryset,如果传入了case_ctx就使用参数脚本替换处理。 没有传入case_ctx默认返回 原始数据
request_body = item.get_request_bodies(case_ctx=case_ctx) 传入case_ctx,再执行下用例。发现替换成功了,但是我们要的是数字,不是字符串。这是被测平台返回的是“72”
导致我们转json的时候就会有异常
我们之前给用户开放了 'parse': common.parse_json_like 的方法 它可以把字符串解析成python的数据类型。只要让用户在后置处理时使用就可以 把“72”变成72了
回到 获取验证码答案里,后置处理改下 #{case_ctx['code'] = parse(result['values'])}
重新运行用例,成功了
收尾和优化
之前我们设计的传入case_ctx才能去调用python表达式,如果用户不传入只单纯使用表达式呢?请求头、请求参数也需要表达式呢?
def get_request_bodies(self, case_ctx=None):
result = {}
for p in self.request_bodies.all(): # type: CaseApiDefRequestBody
if self.api.body_type in ('raw-json', 'raw-xml', 'raw-text'):
# 让用户能用表达式获取上下文
return u.common.proc_param_expression(p.raw_value, case_ctx)
param_name = u.common.proc_param_expression(p.param_name, case_ctx)
param_value = u.common.proc_param_expression(p.param_value, case_ctx)
result.update({param_name: param_value}) # todo
return result
写好以后试试
报错了
排除后猜测是这里错误
控制台导入这个测试下,报错发现 1的位置必须是字符串形式,改成 “1” 就正常了
这样就ok了
登陆成功后我们要获取ttoken,方便其他接口的校验
这是就需要用户写一下后置处理的脚本了
#{caes_ctx['token']= parse(result['text'])['token'][7]}
caes_ctx中 加入一个token,取的值是接口执行requests返回的result.text,然后找到token键,取第七位之后的字符串
这样就ok了
目前有个问题,现在perform_api只支持bearer
最后再用json schema校验获取用户列表返回的json格式正常不
{
"type": "object",
"required": ["content"],
"properties": {
"content": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "username"],
"properties": {
"id": {
"type": "number"
},
"username": {
"type": "string"
},
"roles": {
"type": "array",
"items": {
"type": "object",
"required": [],
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
},
"level": {
"type": "number"
},
"dataScope": {
"type": "string"
}
}
}
},
"job": {
"type": "object",
"required": [],
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
}
}
},
"dept": {
"type": "object",
"required": [],
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
}
}
},
"deptId": {
"type": "number"
},
"createTime": {
"type": "number"
}
}
}
},
"totalElements": {
"type": "number"
}
}
}
最后看下这个问题,现在是程序可以调用。但是pycharm不能识别过去
只需要在utils包的ini.py里加上 __all__ = ['common', 'http', 'redis_', 'resp']
需要注意的是,以后包里有其他模块以后也要写进去,不然以后 from utils import * 的时候就没有新模块。一般不建议 import *