这是一个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。