接口测试 [JsonSchema] 关于接口测试 Json 格式比对核心算法实现 (Java 版)

CC · 2017年07月05日 · 最后由 大鹏 回复于 2020年02月20日 · 4024 次阅读

引言

为什么要自己重新造轮子,而不是采用第三方的 JsonSchema 方法进行实现
存在以下痛点:
1.我之前在网上找了很久,没有找到 java 版直接进行 jsonschema 生成的方法或直接比较的方法
2.http://JSONschema.net/#/home 使用这块框架,必须要先把我们的 Json 信息复制到该网页,然后通过该网页生成的 jsonschema 格式文件写到本地,效率实在过于低下
3.其次我相信很多人都已经实现这块方法,但一直没有开源出来,在此小弟做个抛砖引玉

设计思路

1.比较 JSON 的 Value 值是否匹配(在我个人看来有一定价值,但还不够,会引发很多不必要但错误)
2.能比较 null 值,比如 期望 json 中某个值是有值的,而实际 json 虽然存在这个 key,但它的 value 是 null
3.能比较 key 值是否存在
4.能比较 jsonArray;

其中针对 jsonArray 要展开说明下;
1.对于 jsonArray 内所有的 jsonObject 数据肯定是同一类型的,因此我这边做的是只比较 jsonArray 的第一个 JsonObject
2.对于 jsonArray,大家可能会关心期望长度和实际长度是否有差异

总的而言,采用递归思路进行实现

现在直接附上代码,已实现 generateJsonSchema 方法直接把 json 信息 转换成 jsonschema,再结合比对函数 diffFormatJson,自动校验 jsonschema

package com.testly.auto.core;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.junit.Test;

import java.util.Iterator;

/**
 * Created by 古月随笔 on 2017/6/23.
 */
public class DiffMethod {

   /**
     * 返回当前数据类型
     * @param source
     * @return
     */
    public String getTypeValue(Object source){

        if(source instanceof String){
            return "String";
        }

        if(source instanceof Integer){
            return "Integer";
        }

        if(source instanceof Float){
            return "Float";
        }

        if(source instanceof Long){
            return "Long";
        }

        if(source instanceof Double){
            return "Double";
        }

        if(source instanceof Date){
            return "Date";
        }

        if(source instanceof Boolean){
            return "Boolean";
        }

        return "null";
    }


    /**
     * 把Object变成JsonSchema
     * @param source
     * @return
     */
    public Object generateJsonSchema(Object source){

        Object result = new Object();

        //判断是否为JsonObject
        if(source instanceof JSONObject){
            JSONObject jsonResult = JSONObject.fromObject(result);
            JSONObject sourceJSON = JSONObject.fromObject(source);
            Iterator iterator = sourceJSON.keys();
            while (iterator.hasNext()){
                String key = (String) iterator.next();
                Object nowValue = sourceJSON.get(key);

                if(nowValue == null || nowValue.toString().equals("null")){
                    jsonResult.put(key,"null");

                }else if(isJsonObject(nowValue)){
                    jsonResult.put(key,generateJsonSchema(nowValue));
                }else if(isJsonArray(nowValue)){
                    JSONArray tempArray = JSONArray.fromObject(nowValue);
                    JSONArray newArray = new JSONArray();

                    if(tempArray != null && tempArray.size() > 0 ){
                        for(int i = 0 ;i < tempArray.size(); i++){
                            newArray.add(generateJsonSchema(tempArray.get(i)));
                        }
                        jsonResult.put(key,newArray);
                    }
                }else if(nowValue instanceof List){
                    List<Object> newList = new ArrayList<Object>();

                    for(int i = 0;i<((List) nowValue).size();i++){
                        newList.add(((List) nowValue).get(i));
                    }

                    jsonResult.put(key,newList);
                }else {

                    jsonResult.put(key,getTypeValue(nowValue));
                }

            }
            return jsonResult;
        }


        if(source instanceof JSONArray){
            JSONArray jsonResult = JSONArray.fromObject(source);
            JSONArray tempArray = new JSONArray();
            if(jsonResult != null && jsonResult.size() > 0){
                for(int i = 0 ;i < jsonResult.size(); i++){
                    tempArray.add(generateJsonSchema(jsonResult.get(i)));
                }
                return tempArray;
            }

        }

        return getTypeValue(source);

    }



    /**
     * JSON格式比对
     * @param currentJSON
     * @param expectedJSON
     * @return
     */
    public JSONObject diffJson(JSONObject currentJSON,JSONObject expectedJSON){

        JSONObject jsonDiff = new JSONObject();

        Iterator iterator = expectedJSON.keys();

        while (iterator.hasNext()){
            String key = (String)iterator.next();
            Object expectedValue = expectedJSON.get(key);
            Object currentValue = currentJSON.get(key);
            if(!expectedValue.toString().equals(currentValue.toString())){
                JSONObject tempJSON = new JSONObject();
                tempJSON.put("value",currentValue);
                tempJSON.put("expected",expectedValue);
                jsonDiff.put(key,tempJSON);
            }
        }
        return jsonDiff;
    }


    /**
     * 判断是否为JSONObject
     * @param value
     * @return
     */
    public boolean isJsonObject(Object value){

        try{
            if(value instanceof JSONObject) {
                return true;
            }else {
                return false;
            }
        }catch (Exception e){
            return false;
        }
    }


    /**
     * 判断是否为JSONArray
     * @param value
     * @return
     */
    public boolean isJsonArray(Object value){

        try{

            if(value instanceof JSONArray){
                return true;
            }else {
                return false;
            }

        }catch (Exception e){
            return false;
        }
    }


    /**
     * JSON格式比对,值不能为空,且key需要存在
     * @param current
     * @param expected
     * @return
     */
    public JSONObject diffFormatJson(Object current,Object expected){

        JSONObject jsonDiff = new JSONObject();

        if(isJsonObject(expected)) {

            JSONObject expectedJSON = JSONObject.fromObject(expected);
            JSONObject currentJSON = JSONObject.fromObject(current);

            Iterator iterator = JSONObject.fromObject(expectedJSON).keys();

            while (iterator.hasNext()) {
                String key = (String) iterator.next();
                Object expectedValue = expectedJSON.get(key);

                if (!currentJSON.containsKey(key)) {
                    JSONObject tempJSON = new JSONObject();
                    tempJSON.put("actualKey", "不存在此" + key);
                    tempJSON.put("expectedKey", key);
                    jsonDiff.put(key, tempJSON);

                }

                if (currentJSON.containsKey(key)) {

                    Object currentValue = currentJSON.get(key);

                    if (expectedValue != null && currentValue == null || expectedValue.toString() != "null" && currentValue.toString() == "null") {
                        JSONObject tempJSON = new JSONObject();
                        tempJSON.put("actualValue", "null");
                        tempJSON.put("expectedValue", expectedValue);
                        jsonDiff.put(key, tempJSON);
                    }

                    if (expectedValue != null && currentValue != null) {
                        if (isJsonObject(expectedValue) && !JSONObject.fromObject(expectedValue).isNullObject() || isJsonArray(expectedValue) && !JSONArray.fromObject(expectedValue).isEmpty()) {
                            JSONObject getResultJSON = new JSONObject();
                            getResultJSON = diffFormatJson(currentValue, expectedValue);
                            if (getResultJSON != null) {
                                jsonDiff.putAll(getResultJSON);
                            }
                        }
                    }
                }
            }
        }

        if(isJsonArray(expected)){
            JSONArray expectArray = JSONArray.fromObject(expected);
            JSONArray currentArray = JSONArray.fromObject(current);

            if(expectArray.size() != currentArray.size()){
                JSONObject tempJSON = new JSONObject();
                tempJSON.put("actualLenth",currentArray.size());
                tempJSON.put("expectLenth",expectArray.size());
                jsonDiff.put("Length",tempJSON);
            }

            if(expectArray.size() != 0){
                Object expectIndexValue = expectArray.get(0);
                Object currentIndexValue = currentArray.get(0);

                if(expectIndexValue != null && currentIndexValue != null){
                    if (isJsonObject(expectIndexValue) && !JSONObject.fromObject(expectIndexValue).isNullObject() || isJsonArray(expectIndexValue) && !JSONArray.fromObject(expectIndexValue).isEmpty()) {
                        JSONObject getResultJSON = new JSONObject();
                        getResultJSON = diffFormatJson(currentIndexValue, expectIndexValue);
                        if (getResultJSON != null) {
                            jsonDiff.putAll(getResultJSON);
                        }
                    }
                }
            }
        }

        return jsonDiff;
    }
}

测试验证:


public static void main(String[] args) {

    DiffMethod diffMethod = new DiffMethod();


    String str1 = "{\"status\":201,\"msg\":\"今天您已经领取过,明天可以继续领取哦!\",\"res\":{\"remainCouponNum\":\"5\",\"userId\":\"123123213222\"}}";

    JSONObject jsonObject1 = JSONObject.fromObject(str1);


    String str2 = "{\"status\":201,\"msg2\":\"今天您已经领取过,明天可以继续领取哦!\",\"res\":{\"remainCouponNum\":\"5\",\"userId\":\"123123213222\"}}";

    JSONObject jsonObject2 = JSONObject.fromObject(str2);


    String str3 = "{\"status\":null,\"msg\":\"今天您已经领取过,明天可以继续领取哦!\",\"res\":{\"remainCouponNum\":\"5\",\"userId\":\"123123213222\"}}";

    JSONObject jsonObject3 = JSONObject.fromObject(str3);

    System.out.println("转换成JSONschame:" + diffMethod.generateJsonSchema(jsonObject1).toString());


    System.out.println("当前str2没有msg字段: " + diffMethod.diffFormatJson(jsonObject2,jsonObject1).toString());

    System.out.println("当前str2中的status为null值:" + diffMethod.diffFormatJson(jsonObject3,jsonObject1).toString());


}

测试结果:

转换成JSONschame:{"status":"Integer","msg":"String","res":{"remainCouponNum":"String","userId":"String"}}
当前str2没有msg字段: {"msg":{"actualKey":"不存在此msg","expectedKey":"msg"}}
当前str2中的status为null值:{"status":{"actualValue":null,"expectedValue":201}}

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 21 条回复 时间 点赞

我想向你推荐 zson:
https://github.com/zhangfei19841004/zson
你要的,都在里面。

CC #21 · 2017年07月05日 Author
再见理想 回复

你这个 zson 我用过,我要的是整个结构的自动比对,而不是我要指定字段进行比对;指定字段比对,我个人感觉还是 jsonPath 比较好玩

CC 回复

后面进行了更新,所有的结构及路径都放在了一个 LIST 里面了,不过,你的场景而言,仍然需要进行重新循环比较一下。
PS:多谢支持。😀

CC #4 · 2017年07月05日 Author
再见理想 回复

从这个角度而言,😀 你也可以尝试输出 这块 jsonschema 的格式比较方法,大家希望的是拿来即用,减少在代码中还要循环做判断,我们要的是结果,符合还是不符合,不符合输出不符合的地方

CC 回复

我的初衷是 zson 不做任何比较或者断言类的。。。只关注于对 JSON 串的操作。。😀

CC #17 · 2017年07月05日 Author
再见理想 回复

😀 你的想法没问题,专制做一块,跟做产品一样,咱们测试要抓住咱们测试做自动化的痛点,所以我才考虑造轮子

刚接触 jsonSchema,作者能不能给个例子调用的例子啊,

CC #9 · 2017年11月07日 Author
张6侠 回复
public static void main(String[] args) {

    DiffMethod diffMethod = new DiffMethod();


    String str1 = "{\"status\":201,\"msg\":\"今天您已经领取过,明天可以继续领取哦!\",\"res\":{\"remainCouponNum\":\"5\",\"userId\":\"123123213222\"}}";

    JSONObject jsonObject1 = JSONObject.fromObject(str1);


    String str2 = "{\"status\":201,\"msg2\":\"今天您已经领取过,明天可以继续领取哦!\",\"res\":{\"remainCouponNum\":\"5\",\"userId\":\"123123213222\"}}";

    JSONObject jsonObject2 = JSONObject.fromObject(str2);


    String str3 = "{\"status\":null,\"msg\":\"今天您已经领取过,明天可以继续领取哦!\",\"res\":{\"remainCouponNum\":\"5\",\"userId\":\"123123213222\"}}";

    JSONObject jsonObject3 = JSONObject.fromObject(str3);

    System.out.println("转换成JSONschame:" + diffMethod.generateJsonSchema(jsonObject1).toString());


    System.out.println("当前str2没有msg字段: " + diffMethod.diffFormatJson(jsonObject2,jsonObject1).toString());

    System.out.println("当前str2中的status为null值:" + diffMethod.diffFormatJson(jsonObject3,jsonObject1).toString());


}

结果显示如下:

转换成JSONschame:{"status":"Integer","msg":"String","res":{"remainCouponNum":"String","userId":"String"}}
当前str2没有msg字段: {"msg":{"actualKey":"不存在此msg","expectedKey":"msg"}}
当前str2中的status为null值:{"status":{"actualValue":null,"expectedValue":201}}

CC 回复

明白了。非常实用,深深的理解那句咱们测试做自动化的痛点~
但对于实际运用到接口测试中如何组织管理那么多接口,才是 me 最大的痛点,不知能否分享指导下
eg:
1、独立的业务接口(login)
2、业务场景接口(request1-->request2-->request3 前个请求响应作为参数传递至下个请求 )

CC #11 · 2017年11月07日 Author

#10 楼 @zhang6x 登录接口封装成通用接口,在每个接口中调用,针对登录接口可以把返回的 token 或者用户 id 写入到本地文件,其他接口读取这个文件信息;业务接口就更简单了,把每个接口的返回结果作为对象返回,把一个业务场景需要的接口对应的组合就可以了

—— 来自 TesterHome 官方 安卓客户端

CC 回复

生成出来还是很棒的。但是比如要指定字段是否必须 required 这样的属性还是得人工修改 json-schema。而且很多时候业务逻辑相关的一些字段,需要限制值得范围,或者指定为 enum 类型,这些好像都需要在自动生成的基础上,进行人工修改。

CC #13 · 2017年11月08日 Author
willys 回复

你这个讲的是 发请求前的入参,这个也是像你说的是 自动生成 case 要考虑;我这块考虑的是对 response 结果的前一次和当前的比对;😀
针对自动生成 case,我这边也在我们自己的单接口框架中做了实现,测试人员只需填写 2 行数据,就可以自动生成 case,包含生成异常参数 case

CC 回复

但是像你所说,把 1 个业务场景接口组合而成即可,那对这个场景的接口验证如何做?另能不能加个联系方式,便于交流

CC #15 · 2017年11月08日 Author

#14 楼 @zhang6x 527777879 qq

—— 来自 TesterHome 官方 安卓客户端

CC 回复

前一次 response 去生成 json-schema,然后用于校验后一次的 response 数据?这样的校验意义是什么呢?

CC #17 · 2017年11月09日 Author
willys 回复

。。。。。我竟然无言以对😅

这个其实比较适合用 一些动态语言 Python 或者 js 来做 ;比较相当方便 , 可以使用 js 然后在 java 里使用 nashron 引擎 来执行 js 代码 ,过程相当顺畅

我想请教一下,如果两个相同的字段,key 一样且都有值,但 value 不一样,这个是不是不能比较出来,我实验了一把,发现没有这个效果,是我哪里操作不对吗

CC #21 · 2018年09月23日 Author
确良 回复

你可以改造下代码,值的检验不在我这代码内,这块检验主要检验是否存在 key,及 value 的格式,及 value 是否为空

先收藏了,有空使用看看效果,多谢!

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册