项目结构更新:
修改了replaceBodyParams为replaceParams,更加直观
新加了asserExpected函数作为判断断言类似的,接口测试不只要测成功的例子,也要测失败的例子。当初真的是想当然了,这个函数我遇到了些麻烦,会在文章结尾说明下。
首先,放上该项目所用到的包依赖,以免用错包就尴尬了
package com.mhc.wey.core.httpInterface;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mhc.wey.dal.model.HttpInterfaceCaseDO;
import com.subaru.common.entity.BizResult;
import jdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
用到的装饰器:
@Service
@Slf4j
@Lazy
上面用到了两个我司自定义的包,已经标注出来了
com.mhc.wey.dal.model.HttpInterfaceCaseDO为接口测试用例数据(数据库导入)
com.subaru.common.entity.BizResult为消息返回的对象
execute代码如下
BizResult bizResult =null;
//执行请求
BizResult executeHttpRequestResult = executeEncapsulation(httpInterfaceCaseDO);
if (! executeHttpRequestResult.isSuccess()) {
return executeHttpRequestResult;
}
CloseableHttpResponse response = (CloseableHttpResponse) executeHttpRequestResult.getData();
log.info("开始解析Response");
//解析response
try {
if (assertExpected(response, httpInterfaceCaseDO)) {
setReplaceMap(httpInterfaceCaseDO.getResponseBodyTransferedParams(), response);
}else {
bizResult = BizResult.create(null,false,"9999","assertExpected返回错误");
}
}catch (NullPointerException e) {
bizResult = BizResult.create(e,false,"9999","获取response实体时出现Exception");
}
finally {
try {
//关闭连接
response.close();
client.close();
}catch (Exception e) {
bizResult = BizResult.create("BOOM!!!",true,"1000","HTTPClient没有关闭");
}
if (bizResult ==null) {
bizResult = BizResult.create("OK",true,"1000","接口测试通过");
}
}
return bizResult;
函数的逻辑十分清晰
- 先执行请求
- 断言返回值是否符合预期
- 满足断言,则处理Response, 获取响应对象中部分参数的值
executeEncapsulation代码如下
//参数替换
BizResult replaceHeaderParamsResult = replaceParams(httpInterfaceCaseDO.getRequestHeader());
if (! replaceHeaderParamsResult.isSuccess()) {
return replaceHeaderParamsResult;
}else {
httpInterfaceCaseDO.setRequestHeader(replaceHeaderParamsResult.getData().toString());
}
BizResult replaceBodyParamsResult = replaceParams(httpInterfaceCaseDO.getRequestBody());
if (! replaceBodyParamsResult.isSuccess()) {
return replaceBodyParamsResult;
}else {
httpInterfaceCaseDO.setRequestBody(replaceBodyParamsResult.getData().toString());
}
//参数校验
BizResult initResult = init(httpInterfaceCaseDO);
if (! initResult.isSuccess()) {
return initResult;
}
return executeHttpRequest(httpInterfaceCaseDO);
替换请求头和请求体的值,再进行值检验
replaceParams代码如下
if (StringUtils.isNoneBlank(str)) {
JSONObject bodyObject = JSON.parseObject(str);
for (String key:
bodyObject.keySet()) {
String keyget = (String) bodyObject.get(key);
if (keyget.startsWith("$")) {
String replacekey = keyget.substring(keyget.indexOf("{") +1, keyget.indexOf("}")).trim();
if (StringUtils.isNoneBlank(replacekey)) {
String value = (String)ReplaceMap.get(replacekey);
if (StringUtils.isBlank(value)) {
return BizResult.create(null,false,"9999","ReplaceMap取值异常");
}
// 替换请求体内的变量
bodyObject.replace(key, value);
}else {
return BizResult.create(null,false,"9999","ReplaceMap取值异常");
}
}
}
}
return BizResult.create(str,true,"1000","替换RequestBody里面的转义字符成功");
这里是直接传待转换字符串进来,这个字符串为了方便默认是JSONString,然后就是正则处理下。
这里要注意的是
String value = (String)ReplaceMap.get(replacekey);
因为会有其他测试人员在接口测试时,打错了变量名,这样的场景是我们要去避免的
init代码如下
if (! checkParams(httpInterfaceCaseDO)) {
return BizResult.create("CheckParameters not pass",false,"9999","Request参数缺失");
}
//RequestMethod检验
if (! (httpInterfaceCaseDO.getRequestMethod().toUpperCase().contentEquals(HttpEnum.GET ) || httpInterfaceCaseDO.getRequestMethod().toUpperCase().contentEquals(HttpEnum.POST ))) {
return BizResult.create("RequestMethod异常",false,"9999","RequestMethod异常");
}
if (StringUtils.isNotEmpty(httpInterfaceCaseDO.getDataFormat()) && StringUtils.isNotBlank(httpInterfaceCaseDO.getDataFormat().trim())) {
if (! (httpInterfaceCaseDO.getDataFormat().toUpperCase().contentEquals(HttpEnum.STRING) || httpInterfaceCaseDO.getDataFormat().toUpperCase().contentEquals(HttpEnum.JSON) || httpInterfaceCaseDO.getDataFormat().toUpperCase().contentEquals(HttpEnum.FORM) || httpInterfaceCaseDO.getDataFormat().toUpperCase().contentEquals(HttpEnum.MEDIA))) {
//如果没有参数类型,默认设置为Srting
httpInterfaceCaseDO.setDataFormat(HttpEnum.STRING);
}
}
getSessionId(httpInterfaceCaseDO);
return BizResult.create("continue",true,"6666","初始化测试数据成功");
getSessionId代码如下
String sessionId ="";
String body ="";
try {
if (context ==null) {
log.info("context为空");
}
List listOfCookies =context.getCookieStore().getCookies();
for (Cookie cookie:
listOfCookies) {
if (“这里不给看哦”.equals(cookie.getName())) {
return BizResult.create("", true, "1000", "已有sessionId,直接通过");
}
}
}catch (Exception e) {
log.info("日常没登陆");
}
if (! httpInterfaceCaseDO.getRequestUrl().contains("login")) {
return BizResult.create(null, true, "9999", "未登录");
}else {
//涉及我司有关内容,已删除
BizResult executeResult = executeHttpRequest(httpInterfaceCaseDO);
if (! executeResult.isSuccess()) {
return executeResult;
}
CloseableHttpResponse response = (CloseableHttpResponse) executeResult.getData();
if (response.getStatusLine().getStatusCode() != HttpEnum.STATUS_OK) {
return BizResult.create(null, false, "9999", "请求失败");
}
}
return BizResult.create(sessionId, true, "1000", "请求成功");
这里主要依赖了HTTPContext保持连接的特性,在单线程内,只要不close掉的话,会一直存储Cookie和其他需要的值
executeHttpRequest代码如下
CloseableHttpResponse response =null;
HttpEntity entity =null;
//请求协议
String requestType = httpInterfaceCaseDO.getRequestUrl().substring(0, httpInterfaceCaseDO.getRequestUrl().indexOf(":", 1)).trim();
// 实例化httpclient
BizResult requestResult = switchType(requestType);
if (!requestResult.isSuccess()) {
return requestResult;
}
// 判断请求类型
try {
switch (httpInterfaceCaseDO.getRequestMethod().toUpperCase()) {
case HttpEnum.GET:
HttpGet httpGet =null;
// 判断是否有请求体
if (StringUtils.isNotEmpty(httpInterfaceCaseDO.getRequestBody()) && StringUtils.isNotBlank(httpInterfaceCaseDO.getRequestBody().trim())) {
String body = httpInterfaceCaseDO.getRequestBody().trim();
// url为转换后的请求体
StringBuilder url =new StringBuilder();
// 判断请求体是否是json格式的数据
if (body.startsWith("{")) {
JSONObject jsonObject = JSON.parseObject(body);
for (String key :
jsonObject.keySet()) {
if (StringUtils.isNotEmpty(url)) {
url.append("&");
}
url.append(String.format("%s?%s", key, jsonObject.getString(key)));
}
}
// 有请求体的GET
httpGet =new HttpGet(String.format("%s?%s", httpInterfaceCaseDO.getRequestUrl(), url.toString()));
}else {
// 无请求体的GET
httpGet =new HttpGet(httpInterfaceCaseDO.getRequestUrl());
}
if (StringUtils.isNoneBlank(httpInterfaceCaseDO.getRequestHeader())) {
JSONObject headerObject = JSON.parseObject(httpInterfaceCaseDO.getRequestHeader());
for (String key :
headerObject.keySet()) {
httpGet.addHeader(key, (String) headerObject.get(key));
}
}
for (Header header :
defaultHeader()) {
httpGet.addHeader(header);
}
//执行http get请求
response =client.execute(httpGet, context);
break;
case HttpEnum.POST:
HttpPost httpPost =new HttpPost(httpInterfaceCaseDO.getRequestUrl());
if (StringUtils.isEmpty(httpInterfaceCaseDO.getDataFormat())) {
// POST方式需要DataFormat字段,无此字段则抛出异常
throw new ValueException("POST没有DataFormat字段");
}
// 创建请求参数
switch (httpInterfaceCaseDO.getDataFormat().toUpperCase()) {
case HttpEnum.STRING:
entity =new StringEntity(httpInterfaceCaseDO.getRequestBody(), "utf-8");
break;
case HttpEnum.FORM:
List body =new ArrayList();
JSONObject jsonObject = JSON.parseObject(httpInterfaceCaseDO.getRequestBody());
for (String key :
jsonObject.keySet()) {
body.add(new BasicNameValuePair(key, jsonObject.getString(key)));
}
entity =new UrlEncodedFormEntity(body, "utf-8");
break;
// 这里暂时不动,考虑下Entity的类型
case HttpEnum.MEDIA:
//获取分隔符
String[] array = httpInterfaceCaseDO.getRequestHeader().split("=");
String bound = array[1].substring(1, array[1].lastIndexOf("\""));
//添加分割线
// entity = new MultipartRequestEntity();
break;
default:
break;
}
httpPost.setEntity(entity);
if (StringUtils.isNoneBlank(httpInterfaceCaseDO.getRequestHeader())) {
JSONObject headerObject = JSON.parseObject(httpInterfaceCaseDO.getRequestHeader());
for (String key :
headerObject.keySet()) {
httpPost.addHeader(key, (String) headerObject.get(key));
}
}
for (Header header :
defaultHeader()) {
httpPost.addHeader(header);
}
//执行http post请求
response =client.execute(httpPost, context);
break;
default:
break;
}
}catch(IOException | NullPointerException e){
return BizResult.create(e, false, "9999", "请求异常 - " + httpInterfaceCaseDO.getRequestMethod().toUpperCase());
}
return BizResult.create(response, true, "1000", "请求成功");
这块代码也是我负责的项目中最长最重要的一块,你们可以看到我POST的MEDIA方式,是还没写完的,不好意思,写到一半的时候,被告知我司的接口需要用到该场景的次数极少,故不了了之,代码长就长在对String的处理,其实像
JSONObject jsonObject = JSON.parseObject(body);
for (String key :
headerObject.keySet()) {
httpPost.addHeader(key, (String) headerObject.get(key));
}
这种代码是可以的整合的,但写完就不想动了。。。
简书不是支持MarkDown的么,写完一发,看到格式不对,真是哔到gou了
暂时先到这,再熬夜怕是要变强了😭