REST Assured是一个可以简化HTTP Builder顶层 基于REST服务的测试过程的Java DSL(针对某一领域,具有受限表达性的一种计算机程序设计语言)。它支持发起POST,GET,PUT,DELETE,OPTIONS,PATCH和HEAD请求,并且可以用来验证和校对这些请求的响应信息。它的语法非常简洁,是一种专为测试 REST API 而设计的测试框架。
官方文档:
https://github.com/rest-assured/rest-assured/wiki/Usage
中文手册:
https://github.com/RookieTester/rest-assured-doc/blob/master/2016-12-12-【接口测试】rest-assured用户手册中文版.markdown
1.Maven的pom.xml添加 REST-Assured 依赖坐标
<dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency>
maven地址:
https://mvnrepository.com/artifact/io.rest-assured/rest-assured
2.语法格式
given(). XXXX when(). XXXX then(). XXXX
类似于行为驱动开发(Behaviour Driven Development-BDD)中的定义的结构 Given-When-Then,Given:
在某场景下,When:发生什么事件,Then:产生了什么结果。而 REST-Assured 借鉴了这一套描述可以使
得语法更加简洁:
@Test(description = "get请求,参数拼接在url后面") public void getRequest() { given(). // 设置请求:请求头,请求体等 when(). get("http://www.httpbin.org/get?phone=13888888888&pwd=123456"). then(). log().body(); }
响应:
{ "args": { "phone": "13888888888", "pwd": "123456" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-626949ef-6009da1921b20b3b56bc20ad" }, "origin": "112.21.34.139", "url": "http://www.httpbin.org/get?phone=13888888888&pwd=123456" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
@Test(description = "get请求,通过queryParam传入参数") public void getRequest() { given(). queryParam("phone", "13888888888"). queryParam("pwd", "123456"). when(). get("http://www.httpbin.org/get"). then(). log().body(); }
响应:
{ "args": { "phone": "13888888888", "pwd": "123456" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-62694a56-1d9db03434d76e9654b13284" }, "origin": "112.21.34.139", "url": "http://www.httpbin.org/get?phone=13888888888&pwd=123456" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
@Test(description = " form表单参数类型") public void postFormRequest() { given(). formParam("phone", "13888888888"). formParam("pwd", "123456"). contentType("application/x-www-form-urlencoded"). when(). post("http://www.httpbin.org/post"). then(). log().body(); }
如果使用formParam()方法的话,可以不用加上contentType,框架会自动添加。
响应:
{ "args": { }, "data": "", "files": { }, "form": { "phone": "13888888888", "pwd": "123456" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Content-Length": "28", "Content-Type": "application/x-www-form-urlencoded; charset=ISO-8859-1", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-62694c17-21f67614068e25ee25b95a33" }, "json": null, "origin": "112.21.34.139", "url": "http://www.httpbin.org/post" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
@Test(description = " json参数类型") public void postJsonRequest() { String requestBody = "{\n" + "\t\"phone\": \"13888888888\",\n" + "\t\"pwd\": \"123456\"\n" + "}"; given(). body(requestBody). contentType(ContentType.JSON). when(). post("http://www.httpbin.org/post"). then(). log().body(); }
响应:
{ "args": { }, "data": "{\n\t\"phone\": \"13888888888\",\n\t\"pwd\": \"123456\"\n}", "files": { }, "form": { }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Content-Length": "45", "Content-Type": "application/json; charset=UTF-8", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-62694de7-5a9ff83b4d2ece035f62f5b7" }, "json": { "phone": "13888888888", "pwd": "123456" }, "origin": "112.21.34.139", "url": "http://www.httpbin.org/post" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
@Test(description = " xml参数类型") public void postXmlRequest() { String xmlData = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<listeners>\n" + " <listener class-name=\"com.boge.listener.TestFailListener\"></listener>\n" + "</listeners>"; given(). body(xmlData). contentType(ContentType.XML). when(). post("http://www.httpbin.org/post"). then(). log().body(); }
响应:
{ "args": { }, "data": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<listeners>\n <listener class-name=\"com.boge.listener.TestFailListener\"></listener>\n</listeners>", "files": { }, "form": { }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Content-Length": "138", "Content-Type": "application/xml; charset=ISO-8859-1", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-6269505a-20c8451a0c3d627b7b14957b" }, "json": null, "origin": "112.21.34.139", "url": "http://www.httpbin.org/post" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
我们传送大容量的数据到服务端时,我们通常使用 multipart 表单数据技术。rest-assured提供了一个叫做
multiPart 的方法可以让我们指定文件(file)、字节数组(byte-array)、输入流或者是上传文件
@Test(description = " 文件上传类型") public void postFileRequest() { given(). multiPart(new File("E:\\Notes\\1.png")). when(). post("http://www.httpbin.org/post"). then(). log().body(); }
响应:
{ "args": { }, "data": "", "files": { "file": "data:application/octet-stream;base64,iVBORw0KGgoAAAANSUhEUgAABKgAAAFcCAYAAADs5oaNAAAgAElEQVR4AezdC3hUVZ7v/S8ZdnOS4akcHkrfClo4JtgJTbAJ0yGvhjb" }, "form": { }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Content-Length": "62999", "Content-Type": "multipart/form-data; boundary=Ud7BcBwbkM3RSzh0JevpiOKUjKC5O38NN", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-62695230-7998092d01ddb50515e7d340" }, "json": null, "origin": "112.21.34.139", "url": "http://www.httpbin.org/post" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
@Test public void getResponse() { Response response = given(). when(). post("http://www.httpbin.org/post"). then(). log().all().extract().response(); }
响应:
HTTP/1.1 200 OK Date: Wed, 27 Apr 2022 14:47:29 GMT Content-Type: application/json Content-Length: 501 Connection: keep-alive Server: gunicorn/19.9.0 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true { "args": { }, "data": "", "files": { }, "form": { }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Content-Length": "0", "Content-Type": "application/x-www-form-urlencoded; charset=ISO-8859-1", "Host": "www.httpbin.org", "User-Agent": "Apache-HttpClient/4.5.3 (Java/1.8.0_211)", "X-Amzn-Trace-Id": "Root=1-62695781-54a196cc23ac032140a2a1e3" }, "json": null, "origin": "112.21.34.139", "url": "http://www.httpbin.org/post" } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
官网对Gpath的说明
GPath is a path expression language integrated into Groovy which allows parts of nested structured
data to be identified. In this sense, it has similar aims and scope as XPath does for XML. The two
main places where you use GPath expressions is when dealing with nested POJOs or when dealing
with XML
简单来说 GPath 是一种路径表达式语言,类似于 xpath,GPath 不仅可以应用于 XML,还可以应用于
JSON、HTML。
代码:
@Test public void getResponse() { Response response = given(). when(). get("http://www.httpbin.org/json"). then(). log().all().extract().response(); }
响应:
HTTP/1.1 200 OK Date: Wed, 27 Apr 2022 15:20:54 GMT Content-Type: application/json Content-Length: 429 Connection: keep-alive Server: gunicorn/19.9.0 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true { "slideshow": { "author": "Yours Truly", "date": "date of publication", "slides": [ { "title": "Wake up to WonderWidgets!", "type": "all" }, { "items": [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets" ], "title": "Overview", "type": "all" } ], "title": "Sample Slide Show" } } =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
获取响应数据里的具体值:
// 获取author logger.info(response.jsonPath().get("slideshow.author")); // 获取slides里的第一个title logger.info(response.jsonPath().get("slideshow.slides.title[0]")); // 获取slides里的第二个title logger.info(response.jsonPath().get("slideshow.slides.title[1]")); // 获取slides里的所有title List<String> titleList = response.jsonPath().get("slideshow.slides.title"); for (String title: titleList) { logger.info("title is:" + title); }
具体打印结果:
[2022-04-27 23:44:15] [INFO ] [RestAssuredDemo:110] - Yours Truly [2022-04-27 23:44:15] [INFO ] [RestAssuredDemo:112] - Wake up to WonderWidgets! [2022-04-27 23:44:15] [INFO ] [RestAssuredDemo:114] - Overview [2022-04-27 23:44:15] [INFO ] [RestAssuredDemo:118] - title is:Wake up to WonderWidgets! [2022-04-27 23:44:15] [INFO ] [RestAssuredDemo:118] - title is:Overview
代码:
@Test public void getResponseHtml() { Response response = given(). when(). get("https://www.baidu.com"). then(). log().all().extract().response(); }
响应:
HTTP/1.1 200 OK Content-Encoding: gzip Content-Length: 1145 Content-Type: text/html Server: bfe Date: Wed, 27 Apr 2022 15:51:50 GMT <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> <meta name="referrer" content="always"/> <link rel="stylesheet" type="text/css" href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css"/> <title>百度一下,你就知道</title> </head> <body link="#0000cc"> <div id="wrapper"> <div id="head"> <div class="head_wrapper"> <div class="s_form"> <div class="s_form_wrapper"> <div id="lg"> <img hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270" height="129"/> </div> <form enctype="application/x-www-form-urlencoded" method="get" class="fm" id="form" name="f" action="//www.baidu.com/s"> <input type="hidden" name="bdorz_come" value="1"/> <input type="hidden" name="ie" value="utf-8"/> <input type="hidden" name="f" value="8"/> <input type="hidden" name="rsv_bp" value="1"/> <input type="hidden" name="rsv_idx" value="1"/> <input type="hidden" name="tn" value="baidu"/> <span class="bg s_ipt_wr"> <input maxlength="255" type="text" class="s_ipt" id="kw" name="wd" value="value" autocomplete="off" autofocus="autofocus"/> </span> <span class="bg s_btn_wr"> <input type="submit" class="bg s_btn" id="su" value="百度一下" autofocus="autofocus"/> </span> </form> </div> </div> <div id="u1"> <a shape="rect" class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a> <a shape="rect" class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a> <a shape="rect" class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a> <a shape="rect" class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a> <a shape="rect" class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a> <noscript> <a shape="rect" class="lb" href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1" name="tj_login">登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>'); </script> <a shape="rect" class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品</a> </div> </div> </div> <div id="ftCon"> <div id="ftConw"> <p id="lh"> <a shape="rect" href="http://home.baidu.com">关于百度</a> <a shape="rect" href="http://ir.baidu.com">About Baidu</a> </p> <p id="cp"> ©2017 Baidu <a shape="rect" href="http://www.baidu.com/duty/">使用百度前必读</a> <a shape="rect" class="cp-feedback" href="http://jianyi.baidu.com/">意见反馈</a> 京ICP证030173号 <img src="//www.baidu.com/img/gs.gif"/> </p> </div> </div> </div> </body> </html> =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
获取响应数据的具体值:
// 获取head下的title, 文本信息 logger.info(response.htmlPath().get("html.head.title")); // 获取head下的meta, 属性信息 logger.info(response.htmlPath().get("html.head.meta[0].@content"));
具体打印结果:
[2022-04-27 23:58:40] [INFO ] [RestAssuredDemo:130] - 百度一下,你就知道 [2022-04-27 23:58:40] [INFO ] [RestAssuredDemo:132] - text/html;charset=utf-8
代码:
@Test public void getResponseXml() { Response response = given(). when(). get("http://www.httpbin.org/xml"). then(). log().all().extract().response(); }
响应:
HTTP/1.1 200 OK Date: Wed, 27 Apr 2022 16:10:18 GMT Content-Type: application/xml Content-Length: 522 Connection: keep-alive Server: gunicorn/19.9.0 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true <?xml version='1.0' encoding='us-ascii'?> <!-- A SAMPLE set of slides --> <slideshow title="Sample Slide Show" date="Date of publication" author="Yours Truly" > <!-- TITLE SLIDE --> <slide type="all"> <title>Wake up to WonderWidgets!</title> </slide> <!-- OVERVIEW --> <slide type="all"> <title>Overview</title> <item>Why <em>WonderWidgets</em> are great </item> <item/> <item>Who <em>buys</em> WonderWidgets </item> </slide> </slideshow> =============================================== Default Suite Total tests run: 1, Passes: 1, Failures: 0, Skips: 0 ===============================================
获取响应数据的具体值:
// 获取第二个slide下的title, 文本信息 logger.info(response.xmlPath().get("slideshow.slide[1].title")); // 获取第二个slide的type属性, 属性信息 logger.info(response.xmlPath().get("slideshow.slide[1].@type"));
具体打印结果:
[2022-04-28 00:17:01] [INFO ] [RestAssuredDemo:143] - Overview [2022-04-28 00:17:01] [INFO ] [RestAssuredDemo:145] - all