在上一章中,我们写了第一个 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。