• Java Fluent Restful API自动化测试框架


    这是一个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类这样可以方便的管理配置文件,以及获得一些有用的公共方法。

    下面是一些例子:

    1. 如何发一个Get请求

      APIRequest.GET(uri).header("Authorization", token) .invoke().assertStatus(200).assertBodyContains("expectedContent");

    2. 如何使用XML或者Json格式的Payload

      String payload = loadFile("xmlfile.xml");

    3. 如何运行时定制化Payload填充参数

      String payload = String.format(loadFile("jsonfile.json"), "abc", "edf");

    4. 如何做数据分离,在Property文件管理参数

      `String uri = getValue("get.uri");

    核心实现##

    要想使用Fluent Paragraming Model来写case,那么就得让我们所有的包装方法,都能够返回期望的Class对象,更重要的是,我们是想让Request的返回和验证也能参与到Fluent模式的验证,所以在最终调用方法时,APIRequestAPIResponse就要能和谐的过渡到一起。
    所以我们这样定义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/]
    如果您对本文有意见或者建议,欢迎留言

  • 相关阅读:
    练习 : Flink 自定义 Source & Sink to Redis
    SQL 练习
    HiveSQL 常用函数说明
    Java 开发岗
    练习 : Flink 自定义 sink to hbase
    YOLOv6: a singlestage object detection framework dedicated to industrial applications.
    YOLO with Transformers and Instance Segmentation, with TensorRT acceleration
    About A paper list of some recent Transformerbased CV works
    This is an official implementation for "Swin Transformer: Hierarchical Vision Transformer using Shifted Windows".
    Coming from computer vision and new to transformers? Here are some resources that greatly accelerated my learning.
  • 原文地址:https://www.cnblogs.com/jinsdu/p/4606113.html
Copyright © 2020-2023  润新知