一、涉及jar包如下:
mybatis-spring-boot-starter 1.3.0
lombok 1.16.16
okhttp 3.10.0
json-path 2.4.0
二、大概逻辑如下:
1、前端选择执行
2、后端在数据库逐读取所有case的具体信息,逐条执行case。
3、将结果进行储存和反馈给前端。
4、前端对数据进行处理,如果是列表的批量执行,只会刷新case的最后执行状态,如果是单条case的调试执行,会渲染最新的result(接口返回结果)
三、具体实现思路如下:
1、前端选择执行
前端在点击执行时,会进行一次请求,像后端传一个id的list(id即为case在数据库中的存储id)
2、后端在数据库逐读取所有case的具体信息,逐条执行case。
@Override
public ResponseVo excuteRequest(Integer[] ids) {
ResponseVo responseVo = new ResponseVo();
// 获取当前需执行的所有case
List<TestCase> caseList = apiTestCaseMapper.selectTestCaseListByIds(ids);
// 遍历caseList,进行http请求
for (TestCase testCase:caseList){
// 保存响应结果
String result = "";
Response response = ApiTestUtils.doRequest(testCase, GLOBAL_COLLECTION_ID);
try {
result = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
// 保存变量, 调试执行时可以忽略此方法
ApiTestUtils.saveVariable(result, testCase, GLOBAL_COLLECTION_ID);
// 判断是否通过了所有校验条件
if (ApiTestUtils.verifyResult(result, testCase, GLOBAL_COLLECTION_ID)){
testCase.setStatus(Boolean.TRUE);
} else {
testCase.setStatus(Boolean.FALSE);
}
testCase.setResult(result);
// 更新数据库保存的信息
setJsonValue(testCase);
apiTestCaseMapper.updateCase(testCase);
// 将执行后的结果返回给前端
responseVo.setIsSuccess(Boolean.TRUE);
responseVo.setResult(testCase);
}
return responseVo;
}
<select id="selectTestCaseListByIds" resultMap="caseList">
select * from apitest_testcase
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
这里有一个collectionId(集合id),用于后面的集合执行,此处全部默认为0即可。
@Slf4j
public class ApiTestUtils {
/**
* @param testCase 传入完整的case数据
* @param collectionId 集合id,如果传入0则表示为接口调试执行
* @return Response返回结果
*/
public static Response doRequest(TestCase testCase, Integer collectionId){
String url = "http://" + getVariable(testCase.getApiUrl(), collectionId);
if (null != testCase.getApiPort()){
url += ":" + String.valueOf(testCase.getApiPort());
}
if (!testCase.getApiPath().isEmpty()){
url += getVariable(testCase.getApiPath(),collectionId);
}
// 请求方式: POST/GET
String apiMethod = testCase.getApiMethod();
// 请求类型:1.json 2.url form
int bodyType = testCase.getBodyType();
String body = "";
// 连接超时、写入超时、读取超时,均设置为30s,这里不做展开讨论,有兴趣的同学可以搜一下okhttp的用法
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(Config.connectTimeout, TimeUnit.SECONDS)
.writeTimeout(Config.writeTimeout,TimeUnit.SECONDS)
.readTimeout(Config.readTimeout, TimeUnit.SECONDS)
.build();
Request.Builder builder = new Request.Builder();
// 设置header
List<RequestHeaders> headersList = JSON.parseArray(testCase.getHeaderValue(), RequestHeaders.class);
for (RequestHeaders headers:headersList){
builder.header(headers.getHeaderKey(), getVariable(headers.getHeaderValue(), collectionId));
}
// 设置body、mediaType
String mediaTypeValue = "";
if (bodyType == 1){
mediaTypeValue = "application/json;charset=UTF-8";
body = testCase.getJsonValue();
} else if (bodyType == 2){
mediaTypeValue = "application/x-www-form-urlencoded;charset=utf-8";
//TODO urlfrom的body处理
}
// 如果前端没有传body,讲body设置为空string
if (body == null){
body = "";
} else {
body = getVariable(body, collectionId);
}
Request request = null;
if (apiMethod.equals("POST")){
RequestBody requestBody =RequestBody.create(MediaType.parse(mediaTypeValue), body);
request = builder.url(url).post(requestBody).build();
} else if(apiMethod.equals("GET")){
request = builder.url(url).get().build();
}
Response response = null;
try {
response = client.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
/**
*
* @param string 变量名
* @param collectionId 集合id,:为0时只读全局变量,非0时读了全局变量再读集合内变量
* @return 变量值
*/
private static String getVariable(String string,Integer collectionId){
HashMap<String,String> variableMap = ApiTestConfig.globalVariableMap;
// 如果为集合执行,会再次获取集合内变量
if (collectionId != 0){
variableMap.putAll(ApiTestConfig.collectionVariableMap);
}
// 查询string中是否有${KEY}格式的数据,如果有则将其替换为VALUE
if (!string.isEmpty()){
String reg = "\\$\\{.*?}";
Pattern p = Pattern.compile(reg);
Matcher m = p.matcher(string);
// 遍历替换所有的变量
while (m.find()){
String key = m.group().replace("${","").replace("}","");
if (variableMap.containsKey(key)){
string = string.replace(m.group(),variableMap.get(key));
}
}
return string;
} else {
return "";
}
}
/** 保存变量 */
public static void saveVariable(String result, TestCase testCase, Integer collectionId) {
// 调试执行时可以忽略此方法
}
/**
* 遍历ExpectedList,只要有校验不通过的条件测抛FALSE
*/
public static Boolean verifyResult(String responseResult, TestCase testCase, Integer collectionId){
List<Expected> ExpectedList = JSON.parseArray(testCase.getExpectedListValue(), Expected.class);
if (ExpectedList.size() == 0){
if (collectionId != 0){
Assert.assertTrue(Boolean.TRUE);
}
return Boolean.TRUE;
}
Boolean bool = Boolean.FALSE;
for (Expected exp:ExpectedList){
// 提取方式:1.jsonPath 2.正则表达式
int extractMethod = exp.getExtractMethod();
String extractRule = exp.getExtractRule();
// 校验方式:1.equasl 2.contains
int compareType = exp.getCompareType();
String expectedValue = exp.getExpectedValue();
// 实际获取结果
String actualRes = "";
if (extractMethod == 1){
try {
// 这里取到的值如果是bool时,强转会抛错,所以需要用object接一下,再转String
Object o = JsonPath.read(responseResult, extractRule);
actualRes = String.valueOf(o);
} catch (Exception e){
if (collectionId != 0){
Reporter.log("接口返回结果为:" + responseResult);
Assert.fail("预期值取值失败");
}
return Boolean.FALSE;
}
} else if (extractMethod == 2){
try {
Pattern p = Pattern.compile(extractRule);
Matcher m = p.matcher(responseResult);
if (m.find()){
actualRes = m.group();
}
} catch (Exception e){
if (collectionId != 0){
Assert.fail("预期值取值异常");
}
return Boolean.FALSE;
}
}
// 对比预期结果
if (compareType == 1){
if (collectionId != 0){
Assert.assertEquals(actualRes, expectedValue);
}
if (actualRes.equals(expectedValue)){
bool = Boolean.TRUE;
}
} else if (compareType == 2){
if (actualRes.contains(expectedValue)){
bool = Boolean.TRUE;
} else {
if (collectionId != 0){
Assert.fail("预期值:" + expectedValue + "不存在与期望值" + actualRes + "中");
}
}
}
}
return bool;
}
}
3、将结果进行储存和反馈给前端。
service层这四行代码,进行了处理
// 自己封装的方法,给header,formValue,variableList,expectedList等字段赋值
setJsonValue(testCase);
// 更新数据库保存的信息
apiTestCaseMapper.updateCase(testCase);
// 将执行后的结果返回给前端
responseVo.setIsSuccess(Boolean.TRUE);
responseVo.setResult(testCase);
这里只返回了最后一条case的信息
4、前端对数据进行处理
在列表进行批量执行时,拿到后端返回的isSuccess=true时,则执行刷新列表,获取最新的每个接口的执行状态。
在进行单接口调试时,从result中的testCase信息获取接口响应结果及校验结果,重新渲染页面。
上述过程,将前端传数据然后读sql的过程转变为xml驱动testng进行执行,可以替换成一个接口测试框架。