2.数据渲染与校验(data和scm) - Be5yond/pytest_demo GitHub Wiki

业务中经常要有接口B的请求参数依赖接口A的返回中的某个数据这种情况。即上下文的依赖。
需要自动化测试的数据支持变量,代码执行的时候动态替换,提高测试数据的可维护性

数据渲染

数据替换规则

1. 变量替换

  • '{{}}' 测试数据中两个大括号扩起来的数据被认为是一个变量,如:{'DevToken': '{{ token }}' } 会替换成{'DevToken': '1164fd92ea0a3ab'}
    有了变量替换的规则,那么变量的值从哪里来呢,两个来源

1.1 配置文件

不同环境的域名,账号,资源id,db地址等数据。如本demo中的config.ini文件中配置

[env_test]
host = http://httpbin.org
hippo: {
        "user": "wululu",
        "pwd": "pa55word"
       }
cache: {
        "user": "username",
        "pwd": 111111,
        "predefine_id": "20",
        "argument": "beyond"
       }

1.2 上下文数据

实际业务中必然会有的场景是A返回的某个值,作为接口B的请求参数这样的情况。 Req支持使用jmespath语法查询上一个请求返回的数据,讲目标数据放到缓存池中,供后续的请求替换。
jmespath是一个json数据查询语法,如目标数据为:

{"a": {
  "b": {
    "c": [
      {"d": [0, ["target", 2]]},
      {"d": [3, 4]}
    ]
  }
}}

使用a.b.c[0].d[1][0]可取到数据中的target, jmespath功能非常强大,详细教程参考jmespath官网

本例中Req提供statsh方法,将数据缓存赋值给变量。

user.commit_order(data[0])
# 将返回中json数据'json.type'下的值赋值给order_id
user.stash('json.type', 'order_id')
# 后续请求中的'{{ order_id }}' 将被替换成这里缓存的值

2. 函数替换

  • '{%%}' 测试数据中大括号加%括起来的数据被认为是一个函数,执行的时候会替换成函数执行结果,如:{'st_id': '{% random.randint(1, 10) %}'} 会替换成{'st_id': 5}

2.1 数据构造函数

默认引用了faker来生成一些动态的假数据

2.2 自定义替换函数

除了内置的函数和faker提供的功能,特定业务需要自定义的数据生成函数。
可以自定义函数然后放到/comm/tools.py文件中,示例代码如下:

def randstr(l):
    """
    :param l: string length
    :return: a string object
    """
    return ''.join((random.choice(string.ascii_letters) for i in range(l)))


def timestr(**kwargs):
    """
    generate "2017-03-07T00:00+0800" format time string
    :param kwargs: shifting parameters e.g. hours=3  days=-1
    :return:
    """
    now = arrow.now(tz='Asia/Shanghai')
    t = now.shift(**kwargs)
    return t.format('YYYY-MM-DDTHH:mm+0800')

一个完整的测试数据实例,数据:

{ 'key': {
    'company': '{% fake.company() %}',
    'phone': '{% fake.phone_number() %}',
    'timestr': '{% timestr(hours=1) %}'
   },
   'foo': [
       '{{ val }}', 
       {'newkey': '{{ val }}'}
   ]
}

运行时会动态替换成

{ 'key': {
    'company': ' 凌颖信息传媒有限公司', 
    'phone': '13481047148', 
    'timestr': '2020-08-26T17:41+0800'
   },
  'foo': [
      456, 
      {'newkey': 456}
  ]
}

数据校验

一条完整的case必要包含输入数据和预期结果,实现数据与测试代码分离,预期结果的校验也不能再代码中编写assert语句。
将预期结果的校验方法和测试输入数据成对放到一起管理,可以有两种方式来表达数据校验的规则
1.对json结果,采用jasonschema的方式进行校验,本项目采用schema,复杂的校验规则查看该项目文档
2.对于其他返回的结果,如xml格式,先转成json在用schema进行校验 😄

为什么选用schema而不是jsonschema?

答案是😀一致性和😀易用性

举个例子,如果我需要对如下数据进行校验

data = { 'type': 2 }

# 使用schema
Schema({'type': int}).validate(data)

# 使用jsonschema
schema = {
    "type" : "object",
    "properties" : {
        "type" : {"type" : "number"}
    },
}
validate(instance=data, schema=schema)

同样是校验数据类型是不是int,jsonschema的模板破坏了原有的数据结构,而schema的模板可以和校验数据保持一致。
另外,对于更加复杂的校验逻辑,使用jsonschema模板规则描述会更加复杂的多,而利用schema的灵活性,使用python 函数来校验,减少了jasonschema语法的学习的成本。校验函数也支持自定义函数,减少模板的编写难度。也为校验增加了😀灵活性

一个复杂的校验

如:需要对前面渲染出来的结果进行校验

{ 'key': {
    'company': ' 凌颖信息传媒有限公司', 
    'phone': '13481047148', 
    'timestr': '2020-08-26T17:41+0800'
   },
  'foo': [
      456, 
      {'newkey': 456}
  ]
}

对应的的schema为

{ 'key': {
    'company': str,          # key.company 返回是一个字符串
    'phone': is_valid_phone  # 调用is_valid_phone对key.phone字段进行校验,自定义函数同样写在 /comm/tools.py中
   },                        # 模板中没有的字段,对timestr不进行校验
   'foo': [
       456,                  # 校验foo[0] == 456
       {'newkey': 456}       # 校验foo.newkey == 456
   ]
}

数据存储

因为schema的校验模板不是json数据(包含了python数据类型,函数等),存储的时候也是需要做一些规则转换,转换规则同数据的渲染一致。

schema模板数据存储为

{'key': {
    'company': '{% str %}',          # 函数
    'phone': '{% is_valid_phone %}'  # 自定义函数
   },
   'foo': [
       '{{ val }}',                  # 变量
       {'newkey': '{{ val }}'}       # 变量
   ]
}