一:什么是JSON Schema
JSON Schema,也称为JSON模式。JSON Schema是描述你的JSON数据格式;JSON模式(应用程序/模式+ JSON)有多种用途,其中之一就是实例验证。验证过程可以是交互式或非交互式的。
例如,应用程序可以使用JSON模式来构建用户界面使互动的内容生成除了用户输入检查或验证各种来源获取的数据。 一般使用JSON Schema来进行JSON数据格式验证,在数据提交到业务层次之前进行JSON格式的验证。
JSON Schema是基于JSON格式定义JSON数据结构的规范,用于描述现有的数据格式(JSON数据),清晰的人机可读文档:定义的JSON Schema具有人类和机器都可读的特性,使用JSON Schema可完成完整的JSON结构和数据验证,可用于自动化测试和确保客户提交的数据质量(如下我们只讨论在自动化测试中的应用)。
二、pom.xml 依赖引入
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.2.6</version>
</dependency>
三、抽象JsonSchemaUtils工具类
package utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jackson.JsonNodeReader;
import com.github.fge.jsonschema.core.report.ProcessingMessage;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import org.testng.Assert;
import org.testng.Reporter;
import java.io.IOException;
import java.io.InputStream;
/**
* JsonSchema工具类
*/
public class JsonSchemaUtil {
/**
* 从指定路径读取Schema信息
*
* @param filePath Schema路径
* @return JsonNode型Schema
* @throws IOException 抛出IO异常
*/
private static JsonNode readJSONfile(String filePath) throws IOException {
InputStream stream = JsonSchemaUtil.class.getClassLoader().getResourceAsStream(filePath);
return new JsonNodeReader().fromInputStream(stream);
}
/**
* 将Json的String型转JsonNode类型
*
* @param str 需要转换的Json String对象
* @return 转换JsonNode对象
* @throws IOException 抛出IO异常
*/
private static JsonNode readJSONStr(String str) throws IOException {
return new ObjectMapper().readTree(str);
}
/**
* 将需要验证的JsonNode 与 JsonSchema标准对象 进行比较
*
* @param schema schema标准对象
* @param data 需要比对的Schema对象
*/
private static void assertJsonSchema(JsonNode schema, JsonNode data) {
ProcessingReport report = JsonSchemaFactory.byDefault().getValidator().validateUnchecked(schema, data);
if (!report.isSuccess()) {
for (ProcessingMessage aReport : report) {
Reporter.log(aReport.getMessage(), true);
}
}
Assert.assertTrue(report.isSuccess());
}
/**
* 将需要验证的response 与 JsonSchema标准对象 进行比较
*
* @param schemaPath JsonSchema标准的路径
* @param responseJson 需要验证的Json数据
* @throws IOException 抛出IO异常
*/
public static void assertResponseJsonSchema(String schemaPath, JSONObject responseJson) throws IOException {
String response = JSON.toJSONString(responseJson);
JsonNode jsonSchema = readJSONfile(schemaPath);
JsonNode responseJN = readJSONStr(response);
assertJsonSchema(jsonSchema, responseJN);
}
}
四、Json Schema实例
json中常见的数据类型主要包括
object:
{ "key1": "value1", "key2": "value2" }
array:
[ "first", "second", "third" ]
number:
42
3.1415926
string:
"This is a string"
boolean:
true
false
null:
null
一个示例json格式比如:
{
"fruits": [ "apple", "orange", "pear" ],
"vegetables": [
{
"veggieName": "potato",
"veggieLike": true
},
{
"veggieName": "broccoli",
"veggieLike": false
}
]
}
一个schema实例:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/product.schema.json",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"productId": {
"description": "The unique identifier for a product",
"type": "integer"
},
"productName": {
"description": "Name of the product",
"type": "string"
},
"price": {
"description": "The price of the product",
"type": "number",
"exclusiveMinimum": 0
},
"tags": {
"description": "Tags for the product",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
},
"dimensions": {
"type": "object",
"properties": {
"length": {
"type": "number"
},
"width": {
"type": "number"
},
"height": {
"type": "number"
}
},
"required": [ "length", "width", "height" ]
}
},
"required": [ "productId", "productName", "price" ]
}
分析说明:
说明当前使用的schema版本,可以不包含
"$schema": "http://json-schema.org/draft-07/schema#",
当前schema的唯一id标识,一般指向一个自主域名。方便后续引用,可以不包含
"$id": "http://example.com/product.schema.json",
当前schema的标题,简要描述信息,可不包含
"title": "Product",
详细描述信息,可不包含
"description": "A product from Acme's catalog",
约束对象是object,也就是在 { } 中的数据
"type": "object",
properties定义object里具体属性的约束,description是描述信息,不产生具体约束。
type约束productid属性类型为整型
"properties": {
"productId": {
"description": "The unique identifier for a product",
"type": "integer"
},
约束productName属性类型为字符型
"productName": {
"description": "Name of the product",
"type": "string"
},
约束price属性类型为数字型,可以是整型或浮点型。
exclusiveMinimum约束该数字>0(不包含0)
"price": {
"description": "The price of the product",
"type": "number",
"exclusiveMinimum": 0
},
约束tag属性是array数组。items是数组项约束,这里约束数组项均为字符型
minItems数组至少包含1项。
uniqueItems约束数组中每项不得重复
"tags": {
"description": "Tags for the product",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
},
约束dimensions嵌套对象,其中length,width,height均为数字类型
且这三个字段在dimensions对象中必须包含
"dimensions": {
"type": "object",
"properties": {
"length": {
"type": "number"
},
"width": {
"type": "number"
},
"height": {
"type": "number"
}
},
"required": [ "length", "width", "height" ]
}
},
当前数据对象必须包含productId,productName,price三个字段
"required": [ "productId", "productName", "price" ]
五、新建校验的json文件
resources目录新建schema文件夹,新建LoginTestCase.json文件
{
"description": "登录接口响应信息校验格式",
"type": "object",
"properties": {
"status": {
"type": "integer"
}
},
"required": [
"status"
]
}
六、测试用例编写
package test;
import com.alibaba.fastjson.JSONObject;
import com.jayway.jsonpath.JsonPath;
import config.TestBase;
import model.cases.CasesCase;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import restclient.RestClient;
import utils.DatabaseUtil;
import utils.JsonSchemaUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LoginTestCase extends TestBase {
TestBase testBase;
String host;
String url;
RestClient restClient;
CloseableHttpResponse closeableHttpResponse;
private static String SCHEMA_PATH;
final static Logger Log = LoggerFactory.getLogger(LoginTestCase.class);
@BeforeClass
public void setUp() throws IOException {
testBase = new TestBase();
restClient = new RestClient();
//载入配置文件
host = prop.getProperty("host");
SCHEMA_PATH = "schema/LoginTestCase.json";
SqlSession sqlSession = DatabaseUtil.getSqlSession("databaseConfig.xml");
CasesCase cases = sqlSession.selectOne("model.cases",1);
url = host + cases.getPath();
}
// 从数据库读取login接口用例
@DataProvider(name="date")
public Object[][] providerDate() throws IOException{
//调用工具类传入数据源文件,指定使用的数据源
SqlSession session = DatabaseUtil.getSqlSession("databaseConfig.xml");
List<CasesCase> CasesCaseList = session.selectList("model.caseslist");
Object[][] files = new Object[CasesCaseList.size()][];
for (int i = 0;i < CasesCaseList.size();i++) {
files[i] = new Object[]{CasesCaseList.get(i)};
}
return files;
}
@Test(dataProvider = "date",description = "登录接口")
public void login2(CasesCase CasesCaseList) throws Exception{
Log.info("请求url:" + url);
Reporter.log("请求url:" + url);
//准备请求头信息
HashMap<String,String> headermap = new HashMap<>();
headermap.put("Content-Type", "application/x-www-form-urlencoded");
headermap.put("X-Requested-With", "XMLHttpRequest");
headermap.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36");
headermap.put("Accept-Language", "zh-CN;zh;q=0.8");
headermap.put("Accept-Encoding", "gzip;deflate");
headermap.put("Accept", "application/json;text/javascript;*/*; q=0.01");
//准备请求参数
Map<String,Object> paramsmap = new HashMap<>();
paramsmap.put("username",CasesCaseList.getUsername());
paramsmap.put("password",CasesCaseList.getPassword());
Log.info("请求参数:" + paramsmap);
Reporter.log("请求参数:" + paramsmap);
//发起请求
closeableHttpResponse = restClient.post(url,paramsmap,headermap);
JSONObject responseJson = restClient.getResponseJson(closeableHttpResponse);
//断言状态码是不是200
int statusCode = closeableHttpResponse.getStatusLine().getStatusCode();
Assert.assertEquals(statusCode,RESPNSE_STATUS_CODE_200,"响应码不是200");
Log.info("响应参数:" + responseJson);
Reporter.log("响应参数:" + responseJson);
int status = JsonPath.read(responseJson,"$.status");
//响应返回内容想通过schema标准校验
JsonSchemaUtil.assertResponseJsonSchema(SCHEMA_PATH, responseJson);
Assert.assertEquals(status,CasesCaseList.getJpath(),"用例执行失败");
}
}
七、测试结果:校验成功
测试结果:校验失败
会输出错误信息
八、在线调试Json Schema
同样是利用在线工具来辅助查看。https://jsonschemalint.com/#/version/draft-07/markup/json