通过制定约束文件,通过等价类、边界值、正交法等方法,为单接口自动生成符合内部接口自动化平台导入规范的测试用例。
{
"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"
}
}
]
}
根据上述约束文件生成的某个接口用例示例
{
"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"
}]
}
为生成的属性测试用例制定通用描述,最终应用在接口测试用例标题。如:
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"
}]
}