在测试过程中,有一个重要的工作就是保存记录“现场”,以方便开发人员更快发现BUG解决问题。在接口测试中更是如此,如果开发人员能够根据BUG的信息直接复现请求,是一件很方便的事情。为此我想了一个再框架中增加保存HTTPrequestbase和CloseableHttpResponse两个对象的功能,其中主要是HTTPrequestbase的信息,CloseableHttpResponse以响应内容为主,因为每次请求我都会把必要信息(host,API,HTTP code,响应code,响应时间等等记录)。
下面是更新过的funrequest
类的代码,更新内容时后面几个静态方法:
package com.fun.frame.httpclientimport com.fun.base.bean.RequestInfoimport com.fun.base.exception.RequestExceptionimport com.fun.config.HttpClientConstantimport com.fun.config.RequestTypeimport com.fun.frame.Saveimport com.fun.utils.Timeimport net.sf.json.JSONObjectimport org.apache.commons.lang3.StringUtilsimport org.apache.http.Headerimport org.apache.http.HttpEntityimport org.apache.http.client.methods.HttpPostimport org.apache.http.client.methods.HttpRequestBaseimport org.apache.http.util.EntityUtilsimport org.slf4j.Loggerimport org.slf4j.LoggerFactory/** * 重写FanLibrary,使用面对对象思想 */public class FunRequest extends FanLibrary implements Serializable, Cloneable { private static final long serialVersionUID = -4153600036943378727L; static Logger logger = LoggerFactory.getLogger(FunRequest.class) /** * 请求类型,true为get,false为post */ RequestType requestType /** * 请求对象 */ HttpRequestBase request /** * host地址 */ String host = EMPTY /** * 接口地址 */ String apiName = EMPTY /** * 请求地址,如果为空则由host和apiname拼接 */ String uri = EMPTY /** * header集合 */ List<Header> headers = new ArrayList<>() /** * get参数 */ JSONObject args = new JSONObject() /** * post参数,表单 */ JSONObject params = new JSONObject() /** * json参数 */ JSONObject json = new JSONObject() /** * 响应,若没有这个参数,从将funrequest对象转换成json对象时会自动调用getresponse方法 */ JSONObject response = new JSONObject() /** * 构造方法 * * @param requestType */ private FunRequest(RequestType requestType) { this.requestType = requestType } /** * 获取get对象 * * @return */ static FunRequest isGet() { new FunRequest(RequestType.GET) } /** * 获取post对象 * * @return */ static FunRequest isPost() { new FunRequest(RequestType.POST) } /** * 设置host * * @param host * @return */ FunRequest setHost(String host) { this.host = host this } /** * 设置接口地址 * * @param apiName * @return */ FunRequest setApiName(String apiName) { this.apiName = apiName this } /** * 设置uri * * @param uri * @return */ FunRequest setUri(String uri) { this.uri = uri this } /** * 添加get参数 * * @param key * @param value * @return */ FunRequest addArgs(Object key, Object value) { args.put(key, value) this } /** * 添加post参数 * * @param key * @param value * @return */ FunRequest addParam(Object key, Object value) { params.put(key, value) this } /** * 添加json参数 * * @param key * @param value * @return */ FunRequest addJson(Object key, Object value) { json.put(key, value) this } /** * 添加header * * @param key * @param value * @return */ FunRequest addHeader(Object key, Object value) { headers << getHeader(key.toString(), value.toString()) this } /** * 添加header * * @param header * @return */ public FunRequest addHeader(Header header) { headers.add(header) this } /** * 批量添加header * * @param header * @return */ FunRequest addHeader(List<Header> header) { header.each {h -> headers << h} this } /** * 增加header中cookies * * @param cookies * @return */ FunRequest addCookies(JSONObject cookies) { headers << getCookies(cookies) this } FunRequest setHeaders(List<Header> headers) { this.headers.addAll(headers) this } FunRequest setArgs(JSONObject args) { this.args.putAll(args) this } FunRequest setParams(JSONObject params) { this.params.putAll(params) this } FunRequest setJson(JSONObject json) { this.json.putAll(json) this } /** * 获取请求响应,兼容相关参数方法,不包括file * * @return */ JSONObject getResponse() { response = response.isEmpty() ? getHttpResponse(request == null ? getRequest() : request) : response response } /** * 获取请求对象 * * @return */ HttpRequestBase getRequest() { if (request != null) request; if (StringUtils.isEmpty(uri)) uri = host + apiName switch (requestType) { case RequestType.GET: request = FanLibrary.getHttpGet(uri, args) break case RequestType.POST: request = !params.isEmpty() ? FanLibrary.getHttpPost(uri + changeJsonToArguments(args), params) : !json.isEmpty() ? getHttpPost(uri + changeJsonToArguments(args), json.toString()) : getHttpPost(uri + changeJsonToArguments(args)) break } for (Header header in headers) { request.addHeader(header) } logger.debug("请求信息:{}", new RequestInfo(this.request).toString()) request } @Override FunRequest clone() { def fun = new FunRequest() fun.setRequest(cloneRequest(getRequest())) fun } @Override public String toString() { return "{" + "requestType='" + requestType.getName() + '\'' + ", host='" + host + '\'' + ", apiName='" + apiName + '\'' + ", uri='" + uri + '\'' + ", headers=" + header2Json(headers).toString() + ", args=" + args.toString() + ", params=" + params.toString() + ", json=" + json.toString() + ", response=" + getResponse().toString() + '}'; }/** * 从requestbase对象从初始化funrequest * @param base * @return */ static FunRequest initFromRequest(HttpRequestBase base) { FunRequest request = null; String method = base.getMethod(); RequestType requestType = RequestType.getRequestType(method); String uri = base.getURI().toString(); List<Header> headers = Arrays.asList(base.getAllHeaders()); if (requestType == requestType.GET) { request = FunRequest.isGet().setUri(uri).setHeaders(headers); } else if (requestType == RequestType.POST || requestType == RequestType.FUN) { HttpPost post = (HttpPost) base; HttpEntity entity = post.getEntity(); String value = entity.getContentType().getValue(); String content = null; try { content = EntityUtils.toString(entity); } catch (IOException e) { logger.error("解析响应失败!", e) fail(); } if (value.equalsIgnoreCase(HttpClientConstant.ContentType_TEXT.getValue()) || value.equalsIgnoreCase(HttpClientConstant.ContentType_JSON.getValue())) { request = FunRequest.isPost().setUri(uri).setHeaders(headers).setJson(JSONObject.fromObject(content)); } else if (value.equalsIgnoreCase(HttpClientConstant.ContentType_FORM.getValue())) { request = FunRequest.isPost().setUri(uri).setHeaders(headers).setParams(getJson(content.split("&"))); } } else { RequestException.fail("不支持的请求类型!"); } return request; }/** * 拷贝HttpRequestBase对象 * @param base * @return */ static HttpRequestBase cloneRequest(HttpRequestBase base) { return initFromRequest(base).getRequest() }/** * 保存请求和响应 * @param base * @param response */ public static void save(HttpRequestBase base, JSONObject response) { FunRequest request = initFromRequest(base) request.setResponse(response); Save.info("/request/" + Time.getDate().substring(8) + SPACE_1 + request.getUri().replace(OR, PART), request.toString()); }}复制代码
然后在框架是添加一个key
来控制是否保存响应,然后调用保存方法: if (SAVE_KEY) FunRequest.save(request, res);
其中,res是响应内容,已经解析为json格式,对于非json格式响应做了兼容。同事在保存路径和保存量也做配置初始化的过程中做了校验,这个太简单就不发了。其中一个header2Json
方法是为了解决保存header时候不必须信息太多的问题,内容如下:
/** * 将header转成json对象 * * @param headers * @return */ public static JSONObject header2Json(List<Header> headers) { JSONObject h = new JSONObject(); headers.forEach(x -> h.put(x.getName(), x.getValue())); return h; }复制代码
这个是内容:{requestType='post', host='', apiName='', uri='https://cn.bing.com/search', headers={"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"}, args={}, params={"q":"fun"}, json={}, response={"3242":"234"}}
这样做的好处就是,如果想复现某个出现问题的request,直接从文件中读取保存的request信息,借由funrequest
类对象即可复现这个请求,还可以跟记录的response
做对比。