一、问题背景
我们编写自动化时通过断言取校验返回结果是否符合预期,通常只校验协议状态码httpstatus=200和业务返回体code=0,当对返回数据的要求提高时,如需要校验返回手机号是否符合大陆手机号,返回身份证是否合法等等,传统的断言无法完成。需要引入python第三方库jsonschema来完成返回json结构体的全字段断言,更好的保障业务。
二、常用断言介绍
jmeter
![](https://img.haomeiwen.com/i27710895/8f4c68df9af87114.png)
-
json断言
json断言
-
响应断言
响应断言
-
自定义-beanshell断言
beanshell断言
python/java原生
assert 1==1
unittest
assertEqual(arg1, arg2, msg=None)
java-testng
- Assert
// 断言真假
Assert.assertTrue(true);
// 断言匹配,前一个参数是期望值,后一个参数是实际值
Assert.assertEquals(1, 1);
断言框架
- hamcrest
支持多语言,详细介绍见另外一篇文章
python接口自动化-Hamcrest断言框架 - 简书 (jianshu.com)
三、什么是json
了解JSON Schema之前需要知道什么是json。
json是用于存储和交换数据的语法,是一种轻量级的数据交换格式。
使用场景:
1)接口数据传输
2)序列化
3)配置文件
json与python对比
python | json | 说明 |
---|---|---|
dict | object | 字典 |
list,tuple | array | 序列 |
str | string | 字符串 |
int,float | number | 数字类型 |
True | true | 布尔值True |
False | false | 布尔值False |
None | null | 空值 |
三、什么是jsonschema
JSON Schema 规范(中文版)
JSON Schema 是用于验证 JSON 数据结构的强大工具,Schema可以理解为模式或者规则。详细介绍见上面中文手册。
常用关键字介绍
关键字 | 说明 | 备注 |
---|---|---|
type | 类型 | 支持对象object、数组array、字符串string、整型integer、数字类型number包括整数浮点、布尔bollean、空null |
properties | 属性 | 序列 |
title | 标题 | 注释的作用非必填 |
default | 默认值 | 非必填 |
examples | 示例 | 非必填 |
required | 是否必传 | 必填字段,数组形式,如"required":["字段1","字段2","字段3"] |
maximum | 最大值 | 仅适用于integer或number |
minimum | 最小值 | 仅适用于integer或number |
maxLength | 最大长度 | 仅适用于string |
minLength | 最小长度 | 仅适用于string |
maxProperties | 最大属性数 | 用于object中有多少键值对 |
minProperties | 最小属性数 | 用于object中有多少键值对 |
format | 格式 | draft7中支持,date日期 2018-11-13、 time时间 20:20:39+00:00、日期时间date-time 2018-11-13T20:20:39+00:00、邮箱email xx@xx.com、ip ipv4 xx.xx.xx、网址 uri www.xx.com等 |
pattern | 正则 | 常用于邮箱、身份证、手机号等 |
enum | 枚举 | 常用于邮箱、身份证、手机号等 |
const | 常量 | 固定值 |
uniqueItems | 唯一元素 | 常用于数组,限制数组中每个元素都是唯一 |
items | 元素 | 常用于数组,代表数组中的元素,对元素格式进行限制 |
四、怎么使用jsonschema
下面用个人信息的例子,涵盖主流关键字,并且进行测试,获取校验不通过时的对应报错
# -*- coding: utf-8 -*-
# @Time : 2022/12/18 20:41
# @Author : yanfa
# @File : test_json_schema.py
# @Software: PyCharm
import jsonschema
import pytest
import logging
from jsonschema import Draft7Validator
class TestJsonSchema:
# 模式
schema = {
# 类型
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "大陆居民信息详情",
"description": "主要涉及身份、户籍等",
"default": "这是默认值",
"examples": [
"例子1",
"例子2"
],
# 必传字段列表
"required": [
"name",
"sex",
"address"
],
# 属性
"properties": {
"name": {
"type": "string"
},
"year_for_days": {
"type": "integer",
"multipleOf": 365 # 倍数
},
"youth": {
"type": "integer",
"minimum": 14, # 最小值
"maximum": 35 # 最大值
},
"industry": {
"type": "string",
"minLength": 2, # 最小长度
"maxLength": 10 # 最大长度
},
"birthday": {
"type": "string",
"format": "date" # 格式-日期
},
"email": {
"type": "string",
"format": "idn-email" # 格式-邮箱
},
"url": {
"type": "string",
"format": "uri" # 格式-网址
},
"ip": {
"type": "string",
"format": "ipv4" # 格式-ip
},
"iphone": {
"type": "string",
"pattern": "^(+86)?-?1[3-9]d{9}$" # 正则 手机号
},
"sex": {
"type": "string",
# 枚举
"enum": [
"男",
"女"
]
},
"nationality": {
"type": "string",
"const": "中国" # 常量
},
"car_info": {
"type": "object",
"minProperties": 2, # 最小属性个数
"maxProperties": 3, # 最大属性个数
"properties": {
"number": {
"type": "string"
},
"color": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"child_id": {
"type": "array",
"uniqueItems": True, # 元素是否唯一
# 数组元素
"items": {
"type": "string"
}
}
},
# 嵌套
"address": {
"type": "object",
"properties": {
"detail_address": {
"type": "string"
},
"city": {
"type": "string"
},
"province": {
"type": "string"
}
}
}
}
# 原始json示例
org_instance = {
"name": "李四",
"year_for_days": 365, # 出生天数
"youth": 30, # 青年人年龄
"industry": "建筑工", # 行业
"birthday": "2002-10-01", # 生日
"email": "88888888@qq.com", # 个人邮箱
"url": "http://example.com", # 个人网址
"ip": "10.10.26.40", # 个人ip地址
"iphone": "18988888888", # 大陆手机号
"sex": "男", # 性别
"nationality": "中国", # 国籍
"car_info": {"number": "1234", "color": "black", "type": "suv"}, # 车辆信息
"child_id": ["66666666", "88888888"], # 儿女身份证
"address": { # 地址
"detail_address": "科兴科学园66栋",
"city": "深圳市",
"province": "广东省"
}
}
def validate_case(self):
"""验证是否符合
输入原json
输出是否符合
"""
try:
# 新建format的验证器类。
validator_cls = jsonschema.validators.validator_for(self.schema)
# 验证器实现可以提供一个配置选项来启用format作为断言而不仅仅是注释的功能
validator = validator_cls(self.schema, format_checker=jsonschema.FormatChecker())
# 使用验证器的validate函数来验证data是否符合schema
validator.validate(self.org_instance)
logging.info("JSON实例符合JSON Schema")
except jsonschema.exceptions.ValidationError as err:
logging.error("JSON实例不符合JSON Schema:", err)
def test_normal(self):
"""正常情况"""
self.validate_case()
def test_type(self):
"""类型检查
jsonschema.exceptions.ValidationError: '365' is not of type 'integer'
"""
self.org_instance["year_for_days"] = '365'
self.validate_case()
def test_required(self):
"""必传检查
jsonschema.exceptions.ValidationError: 'address' is a required property
"""
self.org_instance.pop('address')
self.validate_case()
def test_multipleOf(self):
"""倍数检查
jsonschema.exceptions.ValidationError: 370 is not a multiple of 365
"""
self.org_instance["year_for_days"] = 370
self.validate_case()
def test_minimum_maximum(self):
"""最大最小值检查
jsonschema.exceptions.ValidationError: 13 is less than the minimum of 14
jsonschema.exceptions.ValidationError: 36 is greater than the maximum of 35
"""
# 青年人区间为14-35,设置为13,36
# self.org_instance["youth"] = 13
self.org_instance["youth"] = 36
self.validate_case()
def test_format_for_date(self):
"""格式检查-日期
jsonschema.exceptions.ValidationError: '123' is not a 'date'
"""
self.org_instance["birthday"] = "123"
self.validate_case()
def test_format_for_email(self):
"""格式检查-邮箱
jsonschema.exceptions.ValidationError: '2222' is not a 'idn-email'
"""
self.org_instance["email"] = "2222"
self.validate_case()
def test_format_for_uri(self):
"""格式检查-网址"""
# todo 还是有问题
self.org_instance["url"] = "哈哈"
self.validate_case()
def test_format_for_ipv4(self):
"""格式检查-ip
jsonschema.exceptions.ValidationError: '1234' is not a 'ipv4'
"""
self.org_instance["ip"] = "1234"
self.validate_case()
def test_format_for_pattern(self):
"""正则检查-手机号
jsonschema.exceptions.ValidationError: '12346789' does not match '^(\\+86)?-?1[3-9]\\d{9}$'
"""
self.org_instance["iphone"] = "12346789"
self.validate_case()
def test_enum(self):
"""枚举检查-性别
jsonschema.exceptions.ValidationError: '中性' is not one of ['男', '女']
"""
self.org_instance["sex"] = "中性"
self.validate_case()
def test_const(self):
"""常量检查-国籍
jsonschema.exceptions.ValidationError: '中国' was expected
"""
self.org_instance["nationality"] = "美国"
self.validate_case()
def test_minProperties_maxProperties(self):
"""对象属性个数检查-国籍
jsonschema.exceptions.ValidationError: {'number': '1234'} does not have enough properties
jsonschema.exceptions.ValidationError: {'number': '1234', 'color': 'black', 'type': 'suv', 'length': '3'} has too many properties
"""
# 删除一个属性,最低要求2个属性,目前只传一个属性
# self.org_instance["car_info"].pop("color")
# 新增一个属性,最高要求3个属性,目前四个属性
self.org_instance["car_info"]["length"] = "3"
self.validate_case()
def test_items(self):
"""列表元素类型检查-子女身份证
jsonschema.exceptions.ValidationError: 888999 is not of type 'string'"""
# 模式中身份证为字符串,这里传数字
self.org_instance["child_id"][0] = 888999
self.validate_case()
def test_uniqueItems(self):
"""列表元素唯一性检查-子女身份证
jsonschema.exceptions.ValidationError: ['66666666', '66666666'] has non-unique elements"""
# 修改第二个元素与第一个元素相同
self.org_instance["child_id"][1] = "66666666"
self.validate_case()
def test_minLength_maxLength(self):
"""属性长度检查-详细地址
jsonschema.exceptions.ValidationError: '测' is too short
jsonschema.exceptions.ValidationError: '资深高级专业无敌营养设计师' is too long
"""
# 修改长度小于5
# self.org_instance["industry"] = "测"
self.org_instance["industry"] = "资深高级专业无敌营养设计师"
self.validate_case()
if __name__ == '__main__':
# 执行测试用例
pytest.main([
'/Users/yanfademac_pro/PycharmProjects/myProject/data_deal/test_json_schema.py::TestJsonSchema',
'-s'])
网友评论