阶段三-02 用例之间上下文传递

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() 函数用来执行一个字符串表达式,并返回表达式的值。

 Python eval() 函数 | 菜鸟教程

exec 执行储存在字符串或文件中的Python语句,相比于 eval,exec可以执行更复杂的 Python 代码。  exec obj  ;  obj -- 要执行的表达式。

Python exec 内置语句 | 菜鸟教程

上面的代码eval的时候就保存了,改成exec就能正常执行了。我们接到异常后又再抛出异常,感觉不够优雅,干脆直接报错,我们把异常交给调用它的函数来处理

参数传递

以上执行后可以发现我们 case_ctx = {}里uuid值了,那用户要怎么用?我们想要这些字段都能用上

在common里再写个方法,通过getattr循环取 redis_key , auth_username……的值,没取到就继续跑,取到了就给他应该默认的方法,方法暂时没想到

 Python getattr() 函数 | 菜鸟教程

Python setattr() 函数 | 菜鸟教程

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 *

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值