java fluent api_Java Fluent Restful API自动化测试框架

本文介绍了一种使用Java Fluent API构建的RESTful API自动化测试框架,旨在提高测试代码的可读性。该框架基于Jersey Client,支持HTTP请求(GET, POST, PUT, DELETE等),便于响应验证,日志记录,以及数据管理。通过示例展示了如何简洁地编写测试用例。" 84935050,8208449,VC++ MFC实现对话框右键菜单及选中子菜单,"['MFC', 'Windows编程', 'VC++']
摘要由CSDN通过智能技术生成

这是一个Restful API自动化测试框架,这是一个能让你写出高可读性测试代码的测试框架!

项目目标##

话说目前行业内,Restful API自动化测试框架已经不是稀罕物了,各个语言都有自己的实现机制。拿Java的Jersey来讲,它本身就提供了一个API测试框架-Jersey Test Framework.能够帮助我们写API测试,但是这里我们想做的是另一套。

观察到Jersey使用了Fluent interface的模式来让代码可读性更高,比如下面:

String responseMsg = target.path("myresource").request().get(String.class);

那么如果我们也使用Fluent Interface模式,是不是也可以让我们的测试代码可读性更高呢?

比如下面的测试的代码,是不是看起来很清爽,目标更明确呢?

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出错时,我们容易分析问题所在

能够做好数据分离,用配置文件管理测试数据

用到的工具##

显然,框架不是工具,它只是对现有工具的组合和再包装,而这个框架也使用了一些流行的工具:

Jersey Client 2.18 我们要使用它来帮助我们发HTTP Request

Junit4 测试框架,用它来写Case

Apache Commons IO 提供Common API帮助读写文件

SLF4J,打印log怎能少了它

如何使用##

最终,所有的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 params = new HashMap();

private Map headers = new HashMap();

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 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);

}

}

`

源码地址

未完待续

下一步打算结合我的Junit Extension工具,给框架添加灵活管理Case的能力,这样当Case变多时,就可以按需执行我们需要的Case。

参考资料##

如果您看了本篇博客,觉得对您有所收获,请点击下面的 [推荐]

如果您想转载本博客,请注明出处http://www.cnblogs.com/jinsdu/]

如果您对本文有意见或者建议,欢迎留言

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值