Java Fluent Restful API自动化测试框架

发表于:2016-9-23 11:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:大卡尔    来源:51Testing软件测试网采编

  这是一个RestfulAPI自动化测试框架,这是一个能让你写出高可读性测试代码的测试框架!
  项目目标
  话说目前行业内,RestfulAPI自动化测试框架已经不是稀罕物了,各个语言都有自己的实现机制。拿Java的Jersey来讲,它本身就提供了一个API测试框架-JerseyTestFramework.能够帮助我们写API测试,但是这里我们想做的是另一套。
  观察到Jersey使用了Fluentinterface的模式来让代码可读性更高,比如下面:
  StringresponseMsg=target.path("myresource").request().get(String.class);
  那么如果我们也使用FluentInterface模式,是不是也可以让我们的测试代码可读性更高呢?
  比如下面的测试的代码,是不是看起来很清爽,目标更明确呢?
  APIRequest.GET(URL).header("Authorization","Bearer"+token).invoke().assertStatus(200).assertBody(expectedBody);
  直接一行代码,搞定一条Case!
  分析需求
  既然是一个API自动化测试框架,那它能做什么呢?
  · 能够发HTTP请求-Get,Post,Put,Delete,甚至Head
  · 能够接受HTTP返回,并且能够方便验证其返回值
  · 能够打印所有Log,包含Request和Response的所有部分,这样当Case出错时,我们容易分析问题所在
  · 能够做好数据分离,用配置文件管理测试数据
  用到的工具
  显然,框架不是工具,它只是对现有工具的组合和再包装,而这个框架也使用了一些流行的工具:
  · JerseyClient2.18我们要使用它来帮助我们发HTTPRequest
  · Junit4测试框架,用它来写Case
  · ApacheCommonsIO提供CommonAPI帮助读写文件
  · SLF4J,打印log怎能少了它
  如何使用
  最终,所有的HTTPRequest都从APIRequest这个类出发,一步步构建,最终调用Invoke方法发送HTTP请求。
  用APIResoponse来管理HTTP的返回,这个方法提供一些公共的方法来验证API的返回。
  建议所有的TestCase都继承与APITest类这样可以方便的管理配置文件,以及获得一些有用的公共方法。
  下面是一些例子:
  1、如何发一个Get请求
  APIRequest.GET(uri).header("Authorization",token).invoke().assertStatus(200).assertBodyContains("expectedContent");
  2、如何使用XML或者Json格式的Payload
  Stringpayload=loadFile("xmlfile.xml");
  3、如何运行时定制化Payload填充参数
  Stringpayload=String.format(loadFile("jsonfile.json"),"abc","edf");
  4、如何做数据分离,在Property文件管理参数
  `Stringuri=getValue("get.uri");
  核心实现
  要想使用FluentParagramingModel来写case,那么就得让我们所有的包装方法,都能够返回期望的Class对象,更重要的是,我们是想让Request的返回和验证也能参与到Fluent模式的验证,所以在最终调用方法时,APIRequest和APIResponse就要能和谐的过渡到一起。
  所以我们这样定义APIRequest类:
/**
*GeneralClasstomakeHTTPcalls
*
*@authorCarlJi
*/
publicclassAPIRequest{
privateUriBuilderuri;
privateMap<String,String>params=newHashMap<String,String>();
privateMap<String,String>headers=newHashMap<String,String>();
privateMediaTypecontentType=MediaType.APPLICATION_XML_TYPE;
privateMediaTypeacceptType;
privateStringhttpMethod;
privateStringbody;
privateAPIRequest(Stringuri,Stringmethod)
{
this.uri=UriBuilder.fromUri(uri);
this.httpMethod=method;
}
/**
*BuildaHTTPGetrequest
*
*@paramuri
*TheURIonwhichaHTTPgetrequestwillbecalled
*@return
*{@linkAPIRequest}
*/
publicstaticAPIRequestGET(Stringuri)
{
returnnewAPIRequest(uri,HttpMethod.GET);
}
/**
*BuildaHTTPPostrequest
*
*@paramuri
*TheURIonwhichaPOSTrequestwillbecalled
*@return
*{@linkAPIRequest}
*/
publicstaticAPIRequestPOST(Stringuri)
{
returnnewAPIRequest(uri,HttpMethod.POST);
}
/**
*BuildaHTTPPutrequest
*
*@paramuri
*TheURIonwhichaPUTrequestwillbecalled
*@return
*{@linkAPIRequest}
*/
publicstaticAPIRequestPUT(Stringuri)
{
returnnewAPIRequest(uri,HttpMethod.PUT);
}
/**
*BuildaHTTPDeleterequest
*
*@paramuri
*TheURIthattheDeleteRequestwillbecalled
*@return
*{@linkAPIRequest}
*/
publicstaticAPIRequestDELETE(Stringuri)
{
returnnewAPIRequest(uri,HttpMethod.DELETE);
}
/**
*BuildaHTTPHEADrequest
*
*@paramuri
*TheURIthattheHeadrequestwillbecalled
*@return
*{@linkAPIRequest}
*/
publicstaticAPIRequestHEAD(Stringuri)
{
returnnewAPIRequest(uri,HttpMethod.HEAD);
}
/**
*Addthe{@codevalue}totheendofURItobuildthefinalURI
*
*@paramvalue
*ThevaluethatwillbeappendedtotheURI
*@return
*{@linkAPIRequest}
*/
publicAPIRequestpath(Stringvalue)
{
this.uri.path(value);
returnthis;
}
/**
*BuildtheparameterintherequestURI
*
*@paramkey
*TherequestURIparameterkey
*@paramvalue
*TherequestURIparametervalue
*@return
*{@linkAPIRequest}
*/
publicAPIRequestparam(Stringkey,Stringvalue)
{
params.put(key,value);
returnthis;
}
/**
*Setthecontenttypeintherequestbody
*
*@paramtype
*Thecontenttype{@linkMediaType}
*@return
*{@linkAPIRequest}
*/
publicAPIRequesttype(MediaTypetype)
{
this.contentType=type;
returnthis;
}
/**
*SettheacceptedtypefortheHTTPresponsewhencallingthespecificHTTPrequest
*
*@paramtype
*Theacceptedtypefortheresponseofthisrequest
*@return
*{@linkAPIRequest}
*/
publicAPIRequestaccept(MediaTypetype)
{
this.acceptType=type;
returnthis;
}
/**
*SettheHTTPrequestheadersparameter
*
*@paramkey
*Theheadername
*@paramvalue
*Thecorrespondingvaluefortheheader
*@return
*{@linkAPIRequest}
*/
publicAPIRequestheader(Stringkey,Stringvalue)
{
headers.put(key,value);
returnthis;
}
/**
*Settherequestbody
*
*@parambody
*Thebodyoftherequest
*@return
*{@linkAPIRequest}
*/
publicAPIRequestbody(Stringbody)
{
this.body=body;
returnthis;
}
/**
*InvokejerseyclienttosendHTTPrequest
*
*@return{@linkAPIResponse}
*/
publicAPIResponseinvoke()
{
ClientConfigconfig=newClientConfig();
/**
*Important:JerseyInvocationclasswillcheck"EntitymustbenullforhttpmethodDELETE."
*sowecannotsendDELETErequestwithentityinpayload,
*herewesuppressthischeck
*/
config.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION,true);
Clientclient=ClientBuilder.newClient(config);
//Printalllogsforeachrequestandresponse
client.register(newLoggingFilter(Logger.getLogger(APIResponse.class.getName()),true));
WebTargetwebTarget=client.target(uri);
if(!params.isEmpty())
{
for(Entry<String,String>key:params.entrySet())
{
webTarget=webTarget.queryParam(key.getKey(),key.getValue());
}
}
Invocation.BuilderinvocationBuilder=webTarget.request();
if(acceptType!=null)
{
invocationBuilder=invocationBuilder.accept(acceptType);
}
if(!headers.isEmpty())
{
for(Stringkey:headers.keySet())
{
invocationBuilder.header(key,headers.get(key));
}
}
Responseresponse;
if(body==null)
{
response=invocationBuilder.method(httpMethod,Response.class);
}
else
{
response=invocationBuilder.method(httpMethod,Entity.entity(body,contentType),Response.class);
}
returnnewAPIResponse(response);
}
}
  源码地址
  源码已上传Github:https://github.com/CarlJi/RestfulAPITests
  欢迎大家分享讨论,提意见!
  未完待续
  下一步打算结合我的JunitExtension工具,给框架添加灵活管理Case的能力,这样当Case变多时,就可以按需执行我们需要的Case。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号