在上一章中,我們寫了第一個 get 請求的測試類,這一章我們來對它進行初步優化和封裝。
首先想到的問題是,以後我們的接口自動化測試框架會大量用到發送 http 請求的功能。
那麼這一部分的處理,可以將它分離出來,以後的測試類只需要調用請求類的方法實現發送請求和接收響應。
在我們的項目目錄 src/main/java 下,新建一個包名爲 com.mytest.httpclient, 在包下新建一個類,名稱爲 HttpClienUtil。
這個類我們把上一章寫的發送請求和處理反饋的代碼遷移過來並進行了重構:
package com.mytest.httpclient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
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.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class HttpClienUtil {
private CloseableHttpClient httpClient;
private CloseableHttpResponse response;
private RequestConfig requestConfig;
public String HTTPSTATUS = "HttpStatus";
public HttpClientUtil() {
requestConfig = RequestConfig.custom().setConnectTimeout(5000).
setConnectionRequestTimeout(1000).
setSocketTimeout(10000).build();
}
/**
*
* @param connectTimeout 設置連接超時時間,單位毫秒。
* @param connectionRequestTimeout 設置從connect Manager(連接池)獲取 Connection
* 超時時間,單位毫秒。這個屬性是新加的屬性,
因爲目前版本是可以共享連接池的。
* @param socketTimeout 請求獲取數據的超時時間(即響應時間),單位 毫秒。
* 如果訪問一個接口,多少時間內無法返回數據, 就直接放棄此次調用。
*/
public HttpClientUtil(int connectTimeout, int connectionRequestTimeout,
int socketTimeout) {
requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(connectionRequestTimeout)
.setSocketTimeout(socketTimeout).build();
}
public JSONObject sendGet(String url, HashMap<String, String> params, HashMap<String,
String> headers) throws Exception {
httpClient = HttpClients.createDefault();
// 拼接url
if (params != null) {
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : params.entrySet()) {
String value = entry.getValue();
if (!value.isEmpty()) {
pairs.add(new BasicNameValuePair(entry.getKey(),value));
}
}
url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs), "UTF-8");
}
HttpGet httpGet = new HttpGet(url);
try {
httpGet.setConfig(requestConfig);
// 加載請求頭到httpget對象
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpGet.setHeader(entry.getKey(), entry.getValue());
}
}
response = httpClient.execute(httpGet);
// 獲取返回參數
HttpEntity entity = response.getEntity(); String result = null;
if (entity != null) {
result = EntityUtils.toString(entity, "UTF-8");
}
// 釋放請求,關閉連接
EntityUtils.consume(entity);
JSONObject jsonobj = JSON.parseObject(result);
jsonobj.put(HTTPSTATUS,
response.getStatusLine().getStatusCode());
return jsonobj;
} finally {
httpClient.close(); response.close();
}
}
public JSONObject sendGet(String url, HashMap<String, String> params)
throws Exception {
return this.sendGet(url, params, null);
}
public JSONObject sendGet(String url) throws Exception {
return this.sendGet(url, null, null);
}
}
代碼重構後,增加了兩個構造函數,一個使用默認值,一個可以給其傳遞相應的參 數,都是用於實例化 RequestConfig 對象並設置請求的超時時間;也重載了兩個 sendGet 方法,都是用於發送 get 請求並獲取 JSONObject 對象,爲了使用方便可以使用只有一個或兩個參數的 sendGet 方法。
請注意看這行代碼:
jsonobj.put(HTTPSTATUS, response.getStatusLine().getStatusCode());
這裏將響應對應的狀態碼(status code)也壓入了 JSONObject 對象,是爲了方便直 接通過 JSONObject 對象取得響應狀態碼。
後續我們在測試類裏面,直接調用該類的這些方法。
6.2 引入 JSON 解析工具類
爲了對響應結果進行解讀和驗證,我們使用 com.alibaba.fastjson 包中的 JSON 對 象的 parseObject 方法和 JSONObject 對象的相應的獲取鍵值的方法 get+數據類型, 請參見以下代碼:
responseBody = response.getEntity();
//轉爲字符串
String responseBodyString = EntityUtils.toString(responseBody, "utf-8");
// 創建Json對象,把上面字符串序列化成Json對象
JSONObject responseJson = JSON.parseObject(responseBodyString);
// json內容解析
int page = responseJson.getInteger("page");
但由於返回的 JSON 字符串並不總是這麼簡單的格式,有時會有 JSON 數組以及嵌套 的 JSON 字符串,所以我們單獨寫一個類用來解讀 JSON 對象。
下面是這個 get 請求對應的響應 JSON 數據截圖
上圖第一個紅框”page”是一個 JSON 對象,我們可以根據鍵”page”來找到對應的值是 2,而第二個紅框“data”是一個 JSON 數組,並不是一個 JSON 對象,不能直接去拿到裏面的值,需要遍歷數組;第三個紅框”first_name”是在一個嵌套 JSON 字符串中,可以根據裏層 JSON 對象的” first_name”來找到對應的值”Michael”。
下面,我們寫一個 JSON 解析的工具方法類,如果是像第一個紅圈的 JSON 對象, 我們直接返回對應的值,如果是需要解析類似 data 數組裏面的 JSON 對象的值,我們構 造方法默認解析數組第一個元素的內容。
在項目目錄 src/main/java 的包 com.mytest.httpclient 下,新建一個類文件,名稱爲 Util,代碼如下:
package com.mytest.httpclient;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
public class Util {
/**
*
* @param responseJson ,這個變量是拿到響應字符串通過 json 轉換 成 json 對象
* @param jpath,這個 jpath 指的是用戶想要查詢 json 對象的值的路 徑寫法 jpath 寫法舉例:
* 1) per_page
* 2)data[1]/first_name,data 是一個 json 數組,[1]表示索引
* /first_name 表示 data 數組下某一個元素下的 json 對象的名稱爲
first_name
* @return,返回 first_name 這個 json 對象名稱對應的值
*/
public static String getValueByJPath(JSONObject responseJson, String jpath) {
Object obj = responseJson;
for (String s : jpath.split("/")) {
if (!s.isEmpty()) {
if (!(s.contains("[") || s.contains("]"))) {
obj = ((JSONObject) obj).get(s);
} else if (s.contains("[") || s.contains("]")) {
obj = ((JSONArray) ((JSONObject) obj).get(s.split("\\[")[0]))
.get(Integer.parseInt(s.split("\\[")[1].replaceAll("]", "")));
}else if (s.contains("{") || s.contains("}")) {
obj = ((JSONArray) ((JSONObject) obj).get(s.split("\\{")[0]))
.get(Integer.parseInt(s.split("\\{")[1].replaceAll("}", "")));
}
}
}
return obj.toString();
}
}
簡單解釋下上面的代碼,主要是查詢三種 json 對象的的值:
第一種最簡單的,這個 json 對象在整個 json 串的第一層,例如上面截圖中的 per_page,這個 per_page 就是通過 jpath 這個參數傳入,返回的結果就是 2;
第二種 jpath 的查詢,例如我想查詢 data 下第一個用戶信息裏面的first_name 的值,這個時候 jpath 的寫法就是 data[0]/first_name,查詢結果應該是 Michael;
第三種是嵌套 JSON 查詢,例如下方的 JSON 字符串,裏面有兩個嵌套的大括 號,如果想取得 first_name 的值,這個時候 jpath 的寫法就是 data/first_name。