在测试过程中,有一个重要的工作就是保存记录“现场”,以方便开发人员更快发现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 = hostthis}/*** 设置接口地址** @param apiName* @return*/FunRequest setApiName(String apiName) {this.apiName = apiNamethis}/*** 设置uri** @param uri* @return*/FunRequest setUri(String uri) {this.uri = urithis}/*** 添加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) : responseresponse}/*** 获取请求对象** @return*/HttpRequestBase getRequest() {if (request != null) request;if (StringUtils.isEmpty(uri))uri = host + apiNameswitch (requestType) {case RequestType.GET:request = FanLibrary.getHttpGet(uri, args)breakcase 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}@OverrideFunRequest clone() {def fun = new FunRequest()fun.setRequest(cloneRequest(getRequest()))fun}@Overridepublic 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做对比。
原文作者:FunTester;如有侵权,请联系管理员删除。