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、属性释义
- property:用例基础属性对象
- projectId:项目编号(内部接口平台中的项目编号)
- moduleId:模块编号(内部接口平台中的模块编号)
- url:接口地址
- method:请求方式(枚举值:get、post、patch、put、delete)
- desc:接口描述(生成的用例名称会以该属性作为前缀)
- doc:接口文档地址
- headers:请求 headers(json 对象字符串)
- schemaType:约束类型(枚举值:data、json、params,根据该字段确定约束生成的类型)
- validEquivalenceClass:有效等价类专有属性
- caseLevel:用例等级(枚举值:高、中、低)
- asserts:断言列表
- order:断言排序
- desc:断言描述
- type:断言类型(枚举值:json、html、code)
- operator:比较符(枚举值:=、<、>、<=、>=、in、!=、re)
- expect:预期结果
- invalidEquivalenceClass:无效等价类专有属性
- caseLevel:用例等级(枚举值:高、中、低)
- asserts:断言列表
- order:断言排序
- desc:断言描述
- type:断言类型(枚举值:json、html、code)
- operator:比较符(枚举值:=、<、>、<=、>=、in、!=、re)
- expect:预期结果
- schema:为接口中每个字段制定约束
- name:接口字段名称
- desc:字段描述
- type:类型(枚举值:String、Number、InDb、NotInDb、Const、InArray、NotInArray;不同类型的 privateConfig 对象属性不一样)
- globalConfig
- allowNull:是否允许为 null
- allowRepeat:是否允许重复
- privateConfig(type==String)
- allowIllegal:是否允许非法字符
- allowEmpty:是否允许空字符串
- minLen:最小长度(含)
- maxLen:最大长度(含)
- privateConfig(type==Number)
- min:最小值
- max:最大值
- privateConfig(type==InDb || type==NotInDb)
- dbId:数据源编号(Alex 平台中的数据源编号)
- sql:查询语句(仅支持查询单个字段)
- elementType:查询结果返回值类型(枚举值:String、Number、Integer、Float、Double)
- privateConfig(type==Const)
- value:常量值
- privateConfig(type==InArray || type==NotInArray)
- value:数组
- elementType:查询结果返回值类型(枚举值:String、Number、Integer、Float、Double)
2.3、生成结果示例
根据上述约束文件生成的某个接口用例示例文章源自玩技e族-https://www.playezu.com/181335.html
{
"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、核心类
- Description
为生成的属性测试用例制定通用描述,最终应用在接口测试用例标题。如:文章源自玩技e族-https://www.playezu.com/181335.html
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);
}
- Filter
根据 schema[x].type,检查 schema[x].privateConfig 配置项数据合理性,如 type 为 String 时,privateConfig 将检查:文章源自玩技e族-https://www.playezu.com/181335.html
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");
}
}
- Generator
为接口中单个属性,依据等价类、边界值等策略,生成属性测试用例,将结果"枚举化",如当 type 为 String 时,为字段生成的测试用例有:文章源自玩技e族-https://www.playezu.com/181335.html
/**
* 生成字段类型为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 条:文章源自玩技e族-https://www.playezu.com/181335.html
[{
"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]内-重复"
}],
- Rule
根据Generator生成的属性测试用例,根据正交法、笛卡尔积组合成接口测试用例,生成的某条用例如下文章源自玩技e族-https://www.playezu.com/181335.html
[{
"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>值存在于数组内-重复"
}],
- Main
解析 Rule 生成的属性测试用例集合,根据自定义测试用例模版生成符合预期的接口测试用例文章源自玩技e族-https://www.playezu.com/181335.html
{
"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、生成过程
- 读取约束文件的 schema 数组,获取每个约束对象(即接口中的某个属性,如:username);
- 根据约束对象类型(String、Number、InDb、NotInDb、Const、InArray、NotInArray),先对约束属性进行校验(通过 Filter),然后生成属性用例;
- Rule 类依据正交法、笛卡尔积生成组合测试用例;
- Main 类读取测试用例模版,解析 Rule 类生成结果,生成最终的测试用例。
未知地区 1F
支持,可以连上 swagger,自动加上字段判断,一键式生成用例,就是字段太多的时通过笛卡尔生成的测试用例量过大是有接入 swagger 的想法。另外还支持正交法,用例就没那么多啦开源吗?