1、简介

​ 通过制定约束文件,通过等价类、边界值、正交法等方法,为单接口自动生成符合内部接口自动化平台导入规范的测试用例。

2、约束文件

2.1、约束文件示例

{
    "property": {
        "projectId": 4,
        "moduleId": 5,
        "url": "/user/register",
        "method": "post",
        "desc": "用户注册接口",
        "doc": "",
        "headers": "",
        "schemaType": "data",
        "validEquivalenceClass": {
            "caseLevel": "",
            "asserts": [
                {
                    "order": 1,
                    "desc": "http状态码需等于200",
                    "type": "code",
                    "operator": "=",
                    "expect": 200
                },
                {
                    "order": 2,
                    "desc": "接口状态码需等于200",
                    "type": "json",
                    "expression": "$..code",
                    "operator": "=",
                    "expect": 200
                }
            ]
        },
        "invalidEquivalenceClass": {
            "caseLevel": "",
            "asserts": [
                {
                    "order": 1,
                    "desc": "http状态码需等于200",
                    "type": "code",
                    "operator": "=",
                    "expect": 200
                },
                {
                    "order": 2,
                    "desc": "接口状态码不等于200",
                    "type": "json",
                    "expression": "$..code",
                    "operator": "!=",
                    "expect": 200
                }
            ]
        }
    },
    "schema": [
        {
            "name": "username",
            "desc": "用户名",
            "type": "NotInDb",
            "globalConfig": {
                "allowNull": false,
                "allowRepeat": false
            },
            "privateConfig": {
                "dbId": 1,
                "sql": "select username from t_user",
                "elementType": "String"
            }
        },
        {
            "name": "password",
            "desc": "密码",
            "type": "String",
            "globalConfig": {
                "allowNull": false,
                "allowRepeat": true
            },
            "privateConfig": {
                "allowIllegal": true,
                "allowEmpty": false,
                "minLen": 1,
                "maxLen": 10
            }
        },
        {
            "name": "realName",
            "desc": "真实姓名",
            "type": "String",
            "globalConfig": {
                "allowNull": true,
                "allowRepeat": true
            },
            "privateConfig": {
                "allowIllegal": false,
                "allowEmpty": true,
                "minLen": 1,
                "maxLen": 10
            }
        },
        {
            "name": "sex",
            "desc": "性别",
            "type": "InArray",
            "globalConfig": {
                "allowNull": false,
                "allowRepeat": true
            },
            "privateConfig": {
                "value": [0, 1],
                "elementType": "Integer"
            }
        }
    ]
}

2.2、属性释义

2.3、生成结果示例

​ 根据上述约束文件生成的某个接口用例示例

{
    "projectId": 4,
    "moduleId": 5,
    "url": "/user/register",
    "method": "post",
    "desc": "无效等价类 用户注册接口 用户名<username>为null 密码<password>为null 真实姓名<realName>为null 性别<sex>为null",
    "level": "",
    "doc": "",
    "headers": "",
    "data": "{'realName':null,'password':null,'sex':null,'username':null}",
    "asserts": [{
        "expect": 200,
        "type": "code",
        "operator": "=",
        "order": 1,
        "desc": "http状态码需等于200"
    }, {
        "expect": 200,
        "expression": "$..code",
        "type": "json",
        "operator": "!=",
        "order": 2,
        "desc": "接口状态码不等于200"
    }]
}

3、核心类

为生成的属性测试用例制定通用描述,最终应用在接口测试用例标题。如:

protected String desc4Null(String key, String desc) {
    return String.format("%s<%s>为null", desc, key);
}

protected String desc4Empty(String key, String desc) {
    return String.format("%s<%s>为空字符串", desc, key);
}

根据 schema[x].type,检查 schema[x].privateConfig 配置项数据合理性,如 type 为 String 时,privateConfig 将检查:

public void valid4String(Boolean allowIllegal, Boolean allowEmpty, Integer minLen, Integer maxLen) throws ValidException {
    ValidUtil.notNUll(allowIllegal, "type of String field valid error, allowIllegal should not be null");
    ValidUtil.notNUll(allowEmpty, "type of String field valid error, allowEmpty should not be null");
    ValidUtil.notNUll(minLen, "type of String field valid error, minLen should not be null");
    ValidUtil.notNUll(maxLen, "type of String field valid error, maxLen should not be null");
    ValidUtil.isGreaterThanOrEqualsZero(minLen, "type of String field valid error, minLen must greater than or equals 0");
    ValidUtil.isGreaterThanOrEqualsZero(maxLen, "type of String field valid error, maxLen must greater than or equals 0");
    if (minLen.compareTo(maxLen) > 0) {
        throw new ValidException("type of String field valid error, maxLen should greater than or equals minLen");
    }
}

为接口中单个属性,依据等价类、边界值等策略,生成属性测试用例,将结果"枚举化",如当 type 为 String 时,为字段生成的测试用例有:

/**
 * 生成字段类型为String的用例
 * @param key 字段名称
 * @param desc 字段描述
 * @param publicConfig 全局配置
 * @param allowIllegal 是否允许非法字符
 * @param allowEmpty 是否允许为空
 * @param minLen 最小长度
 * @param maxLen 最大长度
 * @return 字段用例
 */
private JSONArray genString(String key, String desc,
                       JSONObject publicConfig,
                       Boolean allowIllegal, Boolean allowEmpty, Integer minLen, Integer maxLen) {

    Boolean allowNull = publicConfig.getBoolean("allowNull");
    Boolean allowRepeat = publicConfig.getBoolean("allowRepeat");

    JSONArray result = new JSONArray();

    //1.null
    if (allowNull) {
        result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4Null(key, desc), null, key));
    } else {
        result.add(model(CaseType.INVALID_EQUIVALENCE_CLASS, description.desc4Null(key, desc), null, key));
    }

    //2.empty
    if (allowEmpty) {
        result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4Empty(key, desc), "", key));
    } else {
        result.add(model(CaseType.INVALID_EQUIVALENCE_CLASS, description.desc4Empty(key, desc), "", key));
    }

    //3.长度
    //A.长度范围内
    String randomLegalString = RandomUtil.randomLegalString(minLen, maxLen);
    result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4Length(key, desc, minLen, maxLen), randomLegalString, key));

    //B.恰好为最大长度
    String randomMax = RandomUtil.randomLegalStringByLength(maxLen);
    while (randomMax.equals(randomLegalString)) {
        randomMax = RandomUtil.randomLegalStringByLength(maxLen);
    }
    result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4EqualsMaxLength(key, desc, maxLen), randomMax, key));

    //C.恰好为最小长度
    String randomMin = RandomUtil.randomLegalStringByLength(minLen);
    while (randomMin.equals(randomLegalString) || randomMin.equals(randomMax)) {
        randomMin = RandomUtil.randomLegalStringByLength(minLen);
    }
    result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4EqualsMinLength(key, desc, minLen), randomMin, key));

    //D.恰好为最大长度+1
    result.add(model(CaseType.INVALID_EQUIVALENCE_CLASS, description.desc4GreaterLength(key, desc, maxLen, 1), RandomUtil.randomLegalStringByLength(maxLen + 1), key));

    //E.恰好为最小长度-1
    result.add(model(CaseType.INVALID_EQUIVALENCE_CLASS, description.desc4LessLength(key, desc, minLen, 1), RandomUtil.randomLegalStringByLength(minLen - 1), key));

    //4.非法字符
    if (allowIllegal) {
        result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4IllegalLength(key, desc, minLen, maxLen), RandomUtil.randomIllegalString(minLen, maxLen), key));
    } else {
        result.add(model(CaseType.INVALID_EQUIVALENCE_CLASS, description.desc4IllegalLength(key, desc, minLen, maxLen), RandomUtil.randomIllegalString(minLen, maxLen), key));
    }

    //5.重复
    if (allowRepeat) {
        result.add(model(CaseType.VALID_EQUIVALENCE_CLASS, description.desc4LengthRepeat(key, desc, minLen, maxLen), randomLegalString, key));
    } else {
        result.add(model(CaseType.INVALID_EQUIVALENCE_CLASS, description.desc4LengthRepeat(key, desc, minLen, maxLen), randomLegalString, key));
    }

    return result;
}

生成结果如下,共计 9 条:

[{
    "type": "invalidEquivalenceClass",
    "key": "password",
    "desc": "密码<password>为null"
}, {
    "type": "invalidEquivalenceClass",
    "value": "",
    "key": "password",
    "desc": "密码<password>为空字符串"
}, {
    "type": "validEquivalenceClass",
    "value": "ngw031",
    "key": "password",
    "desc": "密码<password>长度在区间[1,10]内"
}, {
    "type": "validEquivalenceClass",
    "value": "1kriino6qb",
    "key": "password",
    "desc": "密码<password>长度恰好为最大长度<10>"
}, {
    "type": "validEquivalenceClass",
    "value": "e",
    "key": "password",
    "desc": "密码<password>长度恰好为最小长度<1>"
}, {
    "type": "invalidEquivalenceClass",
    "value": "s2r9s39r96v",
    "key": "password",
    "desc": "密码<password>长度为最大长度<10>+1"
}, {
    "type": "invalidEquivalenceClass",
    "value": "",
    "key": "password",
    "desc": "密码<password>长度为最小长度<1>-1"
}, {
    "type": "validEquivalenceClass",
    "value": ",%)",
    "key": "password",
    "desc": "密码<password>非法字符长度在区间[1,10]内"
}, {
    "type": "validEquivalenceClass",
    "value": "ngw031",
    "key": "password",
    "desc": "密码<password>长度在区间[1,10]内-重复"
}],

根据Generator生成的属性测试用例,根据正交法、笛卡尔积组合成接口测试用例,生成的某条用例如下

[{
    "type": "validEquivalenceClass",
    "value": "21e9jzpaghq63",
    "key": "username",
    "desc": "用户名<username>值不存在于表内"
}, {
    "type": "validEquivalenceClass",
    "value": "x",
    "key": "password",
    "desc": "密码<password>长度恰好为最小长度<1>"
}, {
    "type": "validEquivalenceClass",
    "value": "n7aeqeid1s",
    "key": "realName",
    "desc": "真实姓名<realName>长度恰好为最大长度<10>"
}, {
    "type": "validEquivalenceClass",
    "value": 0,
    "key": "sex",
    "desc": "性别<sex>值存在于数组内-重复"
}],

解析 Rule 生成的属性测试用例集合,根据自定义测试用例模版生成符合预期的接口测试用例

{
  "projectId": 4,
  "moduleId": 5,
  "url": "/user/register",
  "method": "post",
  "desc": "无效等价类 用户注册接口 用户名<username>为null 密码<password>为null 真实姓名<realName>为null 性别<sex>为null",
  "level": "",
  "doc": "",
  "headers": "",
  "data": "{'realName':null,'password':null,'sex':null,'username':null}",
  "asserts": [{
      "expect": 200,
      "type": "code",
      "operator": "=",
      "order": 1,
      "desc": "http状态码需等于200"
  }, {
      "expect": 200,
      "expression": "$..code",
      "type": "json",
      "operator": "!=",
      "order": 2,
      "desc": "接口状态码不等于200"
  }]
}

4、生成过程

  1. 读取约束文件的 schema 数组,获取每个约束对象(即接口中的某个属性,如:username);
  2. 根据约束对象类型(String、Number、InDb、NotInDb、Const、InArray、NotInArray),先对约束属性进行校验(通过 Filter),然后生成属性用例;
  3. Rule 类依据正交法、笛卡尔积生成组合测试用例;
  4. Main 类读取测试用例模版,解析 Rule 类生成结果,生成最终的测试用例。


↙↙↙阅读原文可查看相关链接,并与作者交流