這是一個Restful API自動化測試框架,這是一個能讓你寫出高可讀性測試代碼的測試框架!html
話說目前行業內,Restful API自動化測試框架已經不是稀罕物了,各個語言都有本身的實現機制。拿Java的Jersey來說,它自己就提供了一個API測試框架-Jersey Test Framework.可以幫助咱們寫API測試,可是這裏咱們想作的是另外一套。java
觀察到Jersey使用了Fluent interface的模式來讓代碼可讀性更高,好比下面:git
String responseMsg = target.path("myresource").request().get(String.class);
那麼若是咱們也使用Fluent Interface模式,是否是也可讓咱們的測試代碼可讀性更高呢?
好比下面的測試的代碼,是否是看起來很清爽,目標更明確呢?github
APIRequest.GET(URL).header("Authorization", "Bearer " + token).invoke().assertStatus(200).assertBody(expectedBody);
直接一行代碼,搞定一條Case!web
既然是一個API自動化測試框架,那它能作什麼呢?json
顯然,框架不是工具,它只是對現有工具的組合和再包裝,而這個框架也使用了一些流行的工具:app
最終,全部的HTTP Request都從APIRequest
這個類出發,一步步構建,最終調用Invoke方法發送HTTP 請求。框架
用APIResoponse
來管理HTTP的返回,這個方法提供一些公共的方法來驗證API的返回。工具
建議全部的TestCase都繼承與APITest
類這樣能夠方便的管理配置文件,以及得到一些有用的公共方法。測試
下面是一些例子:
如何發一個Get請求
APIRequest.GET(uri).header("Authorization", token) .invoke().assertStatus(200).assertBodyContains("expectedContent");
如何使用XML或者Json格式的Payload
String payload = loadFile("xmlfile.xml");
如何運行時定製化Payload填充參數
String payload = String.format(loadFile("jsonfile.json"), "abc", "edf");
如何作數據分離,在Property文件管理參數
`String uri = getValue("get.uri");
要想使用Fluent Paragraming Model來寫case,那麼就得讓咱們全部的包裝方法,都可以返回指望的Class對象,更重要的是,咱們是想讓Request的返回和驗證也能參與到Fluent模式的驗證,因此在最終調用方法時,APIRequest
和APIResponse
就要能和諧的過渡到一塊兒。
因此咱們這樣定義APIRequest
類:
/** * General Class to make HTTP calls * * @author Carl Ji */ public class APIRequest { private UriBuilder uri; private Map<String, String> params = new HashMap<String, String>(); private Map<String, String> headers = new HashMap<String, String>(); private MediaType contentType = MediaType.APPLICATION_XML_TYPE; private MediaType acceptType; private String httpMethod; private String body; private APIRequest(String uri, String method) { this.uri=UriBuilder.fromUri(uri); this.httpMethod = method; } /** * Build a HTTP Get request * * @param uri * The URI on which a HTTP get request will be called * @return * {@link APIRequest} */ public static APIRequest GET(String uri) { return new APIRequest(uri, HttpMethod.GET); } /** * Build a HTTP Post request * * @param uri * The URI on which a POST request will be called * @return * {@link APIRequest} */ public static APIRequest POST(String uri) { return new APIRequest(uri, HttpMethod.POST); } /** * Build a HTTP Put request * * @param uri * The URI on which a PUT request will be called * @return * {@link APIRequest} */ public static APIRequest PUT(String uri) { return new APIRequest(uri, HttpMethod.PUT); } /** * Build a HTTP Delete request * * @param uri * The URI that the Delete Request will be called * @return * {@link APIRequest} */ public static APIRequest DELETE(String uri) { return new APIRequest(uri, HttpMethod.DELETE); } /** * Build a HTTP HEAD request * * @param uri * The URI that the Head request will be called * @return * {@link APIRequest} */ public static APIRequest HEAD(String uri) { return new APIRequest(uri, HttpMethod.HEAD); } /** * Add the {@code value} to the end of URI to build the final URI * * @param value * The value that will be appended to the URI * @return * {@link APIRequest} */ public APIRequest path(String value) { this.uri.path(value); return this; } /** * Build the parameter in the request URI * * @param key * The request URI parameter key * @param value * The request URI parameter value * @return * {@link APIRequest} */ public APIRequest param(String key, String value) { params.put(key, value); return this; } /** * Set the content type in the request body * * @param type * The content type {@link MediaType} * @return * {@link APIRequest} */ public APIRequest type(MediaType type) { this.contentType = type; return this; } /** * Set the accepted type for the HTTP response when calling the specific HTTP request * * @param type * The accepted type for the response of this request * @return * {@link APIRequest} */ public APIRequest accept(MediaType type) { this.acceptType = type; return this; } /** * Set the HTTP request headers parameter * * @param key * The header name * @param value * The corresponding value for the header * @return * {@link APIRequest} */ public APIRequest header(String key, String value) { headers.put(key, value); return this; } /** * Set the request body * * @param body * The body of the request * @return * {@link APIRequest} */ public APIRequest body(String body) { this.body = body; return this; } /** * Invoke jersey client to send HTTP request * * @return {@link APIResponse} */ public APIResponse invoke() { ClientConfig config = new ClientConfig(); /** * Important: Jersey Invocation class will check "Entity must be null for http method DELETE." * so we can not send DELETE request with entity in payload, * here we suppress this check */ config.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); Client client = ClientBuilder.newClient(config); //Print all logs for each request and response client.register(new LoggingFilter(Logger.getLogger(APIResponse.class.getName()), true)); WebTarget webTarget = client.target(uri); if(!params.isEmpty()) { for(Entry<String, String> key: params.entrySet()) { webTarget = webTarget.queryParam(key.getKey(), key.getValue()); } } Invocation.Builder invocationBuilder= webTarget.request(); if(acceptType != null) { invocationBuilder = invocationBuilder.accept(acceptType); } if(!headers.isEmpty()) { for(String key: headers.keySet()) { invocationBuilder.header(key, headers.get(key)); } } Response response; if(body == null) { response= invocationBuilder.method(httpMethod, Response.class); } else { response = invocationBuilder.method(httpMethod, Entity.entity(body, contentType), Response.class); } return new APIResponse(response); } }
`
源碼已上傳Github:https://github.com/CarlJi/RestfulAPITests
歡迎你們分享討論,提意見!
下一步打算結合個人Junit Extension工具,給框架添加靈活管理Case的能力,這樣當Case變多時,就能夠按需執行咱們須要的Case。
若是您看了本篇博客,以爲對您有所收穫,請點擊下面的 [推薦]
若是您想轉載本博客,請註明出處大卡的博客[http://www.cnblogs.com/jinsdu/] 若是您對本文有意見或者建議,歡迎留言