項目結(jié)構(gòu)更新:
修改了replaceBodyParams為replaceParams,更加直觀
新加了asserExpected函數(shù)作為判斷斷言類似的,接口測試不只要測成功的例子蹲坷,也要測失敗的例子。當初真的是想當然了,這個函數(shù)我遇到了些麻煩,會在文章結(jié)尾說明下举娩。
首先,放上該項目所用到的包依賴,以免用錯包就尷尬了
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
上面用到了兩個我司自定義的包铜涉,已經(jīng)標注出來了
com.mhc.wey.dal.model.HttpInterfaceCaseDO為接口測試用例數(shù)據(jù)(數(shù)據(jù)庫導入)
com.subaru.common.entity.BizResult為消息返回的對象
execute代碼如下
BizResult bizResult =null;
//執(zhí)行請求
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實體時出現(xiàn)Exception");
}
finally {
try {
//關(guān)閉連接
? ? ? ? response.close();
? ? ? ? client.close();
? ? }catch (Exception e) {
bizResult = BizResult.create("BOOM!!!",true,"1000","HTTPClient沒有關(guān)閉");
? ? }
if (bizResult ==null) {
bizResult = BizResult.create("OK",true,"1000","接口測試通過");
? ? }
}
return bizResult;
函數(shù)的邏輯十分清晰
- 先執(zhí)行請求
- 斷言返回值是否符合預期
- 滿足斷言智玻,則處理Response, 獲取響應對象中部分參數(shù)的值
executeEncapsulation代碼如下
//參數(shù)替換
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());
}
//參數(shù)校驗
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取值異常");
? ? ? ? ? ? ? ? ? ? ? ? }
//? ? ? ? ? ? ? ? ? ? ? ? 替換請求體內(nèi)的變量
? ? ? ? ? ? ? ? ? ? ? ? bodyObject.replace(key, value);
? ? ? ? ? ? ? ? ? ? }else {
return BizResult.create(null,false,"9999","ReplaceMap取值異常");
? ? ? ? ? ? ? ? ? ? }
}
}
}
return BizResult.create(str,true,"1000","替換RequestBody里面的轉(zhuǎn)義字符成功");
這里是直接傳待轉(zhuǎn)換字符串進來吊奢,這個字符串為了方便默認是JSONString,然后就是正則處理下纹烹。
這里要注意的是
String value = (String)ReplaceMap.get(replacekey);
因為會有其他測試人員在接口測試時页滚,打錯了變量名,這樣的場景是我們要去避免的
init代碼如下
? ??if (! checkParams(httpInterfaceCaseDO)) {
return BizResult.create("CheckParameters not pass",false,"9999","Request參數(shù)缺失");
}
//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))) {
//如果沒有參數(shù)類型铺呵,默認設置為Srting
? ? ? ? httpInterfaceCaseDO.setDataFormat(HttpEnum.STRING);
? ? }
}
getSessionId(httpInterfaceCaseDO);
return BizResult.create("continue",true,"6666","初始化測試數(shù)據(jù)成功");
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 {
//涉及我司有關(guān)內(nèi)容,已刪除
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保持連接的特性片挂,在單線程內(nèi)邦马,只要不close掉的話,會一直存儲Cookie和其他需要的值
executeHttpRequest代碼如下
? ??CloseableHttpResponse response =null;
? ? ? ? HttpEntity entity =null;
? ? ? ? //請求協(xié)議
? ? ? ? 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為轉(zhuǎn)換后的請求體
? ? ? ? ? ? ? ? ? ? ? ? StringBuilder url =new StringBuilder();
//? ? ? ? ? ? ? ? ? ? 判斷請求體是否是json格式的數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? ? ? 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);
? ? ? ? ? ? ? ? ? ? }
//執(zhí)行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字段");
? ? ? ? ? ? ? ? ? ? }
// 創(chuàng)建請求參數(shù)
? ? ? ? ? ? ? ? ? ? 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);
? ? ? ? ? ? ? ? ? ? }
//執(zhí)行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方式,是還沒寫完的,不好意思社牲,寫到一半的時候出牧,被告知我司的接口需要用到該場景的次數(shù)極少,故不了了之蛾扇,代碼長就長在對String的處理攘烛,其實像
JSONObject jsonObject = JSON.parseObject(body);
for (String key :
headerObject.keySet()) {
httpPost.addHeader(key, (String) headerObject.get(key));
}
這種代碼是可以的整合的,但寫完就不想動了镀首。坟漱。。
簡書不是支持MarkDown的么更哄,寫完一發(fā)芋齿,看到格式不對,真是嗶到gou了
暫時先到這成翩,再熬夜怕是要變強了??