https://testerhome.com/topics/15161
https://github.com/reese0329/rest_assured
idea 引入包 Alt+Enter
参考
https://testerhome.com/topics/11731
行业成熟方案
更早的发现问题
更快的质量反馈
接口测试不能解决移动端的质量,仍然需要端的测试
❖ 业务需求调研:研发和产品反馈常出问题的业务 Charles 抓包
❖ 接口文档:人工档、swagger自动生成的文档
❖ 代码分析:分析spring等框架的代码
❖ 线上log和数据:线上的生成监控和接 log
❖ 客户端抓包:基于用户角度的接口行为分析
❖ 监听分析:tcpdump+wireshark+har提取工具
❖ 代理分析:charles+burpsuite
❖ 转发分析:修改host域名+反向代理转发
❖ 接口调用的流程分析
❖ 代理抓包
❖ 线上log提取
❖ 用例补充:用流程图和思维导图进行业务建模 覆盖参数 API 及API之间调用的顺序
❖ 正常场景用例 right path
❖ 异常场景用例
❖ 安全和稳定性用例
❖ 简约的接口测试DSL
❖ 支持xml json的结构化解析
❖ 支持xpath jsonpath gpath等多种解析 式
❖ 对spring的支持比较全
❖ 创建maven项
❖ 添加依赖 restassured + junit/testng
❖ 编写用例
❖ 添加断言
❖ 调试
32‘’
❖ 基本请求:get、post、添加header、json请求
❖ 常见的结果:statusCode、html、xml、json、jsonp
❖ 用例调试:log、proxy
given 用户给定的条件
when 标准的action
then 断言
状态码
验证网页标题,返回状态码
import java.util.concurrent.TimeUnit;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThan;
public class DoubanTest {
@Test
public void testGetHtml() {
useRelaxedHTTPSValidation(); //不强行校验http信息
given()
.log().all()
.when()
.get("https://www.douban.com")
.then()
.log().all()
.statusCode(200).body("html.head.title",equalTo("豆瓣"));
}
}
Json断言
json jpath
https://static.javadoc.io/io.rest-assured/json-path/3.3.0/io/restassured/path/json/JsonPath.html
遵循 Groovy GPath 语法
对topic列表id断言
package restassured;
import org.junit.BeforeClass;
import org.junit.Test;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.equalTo;
public class Testerhome {
@BeforeClass
public static void BeforeClass(){
useRelaxedHTTPSValidation();
}
@Test
public void topic(){
given()
.when()
.get("https://testerhome.com/api/v3/topics.json").prettyPeek()
.then()
.body("topics.id[0]",equalTo(18728));
}
}
List
1‘’
package restassured;
import org.junit.BeforeClass;
import org.junit.Test;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.equalTo;
public class Testerhome {
@BeforeClass
public static void BeforeClass(){
useRelaxedHTTPSValidation();
}
@Test
public void topic(){
given()
.when()
.get("https://testerhome.com/api/v3/topics.json").prettyPeek()
.then()
//获取第一个topic的id
// .body("topics.id[0]",equalTo(18739))
//获取最后一个topic的user的login信息
.body("topics[-1].user.login",equalTo("cmlanche-github"))
//获取id为18316的topic的title
.body("topics.find{it.id==18316}.title",
equalTo("[深圳] 微众银行内部人才招聘高级测试工程师 / 测试工程师")); //页面修改
}
}
.body(路径,匹配器)
public T body(String path, Matcher> matcher, Object... additionalKeyMatcherPairs) {
this.responseSpec.body(path, matcher, additionalKeyMatcherPairs);
return (ValidatableResponseOptions)this;
}
属于org.hamcrest库,所有语言都可以用 python版本和java版本
http://hamcrest.org/
Hamcrest comes with a library of useful matchers. Here are some of the most important ones.
Core
anything
- always matches, useful if you don’t care what the object under test is
describedAs
- decorator to adding custom failure description
is
- decorator to improve readability - see “Sugar”, below
Logical
allOf
- matches if all matchers match, short circuits (like Java &&)
anyOf - matches if any matchers match, short circuits (like Java |
) |
not
- matches if the wrapped matcher doesn’t match and vice versa
Object
equalTo
- test object equality using Object.equals
hasToString
- test Object.toString
instanceOf
, isCompatibleType
- test type
notNullValue
, nullValue
- test for null
sameInstance
- test object identity
Beans
hasProperty
- test JavaBeans properties
Collections
array
- test an array’s elements against an array of matchers
hasEntry
, hasKey
, hasValue
- test a map contains an entry, key or value
hasItem
, hasItems
- test a collection contains elements
hasItemInArray
- test an array contains an element
Number
closeTo
- test floating point values are close to a given value
greaterThan
, greaterThanOrEqualTo
, lessThan
, lessThanOrEqualTo
- test ordering
Text
equalToIgnoringCase
- test string equality ignoring case
equalToIgnoringWhiteSpace
- test string equality ignoring differences in runs of whitespace
containsString
, endsWith
, startsWith
- test string matching
hasItems实例:
package restassured;
import org.junit.BeforeClass;
import org.junit.Test;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.hasItems;
public class Json_Testerhome {
@BeforeClass
public static void BeforeClass(){
useRelaxedHTTPSValidation();
}
@Test
public void topic(){
given()
.when()
.get("https://testerhome.com/api/v3/topics.json").prettyPeek()
.then()
//获取第一个topic的id
// .body("topics.id[0]",equalTo(18739))
//获取最后一个topic的user的login信息
// .body("topics[-1].user.login",equalTo("cmlanche-github"))
//获取id为18316的topic的title
// .body("topics.find{it.id==18316}.title",
// equalTo("[深圳] 微众银行内部人才招聘高级测试工程师 / 测试工程师")) //页面修改
//hasItems实例
.body("topics.id",hasItems(18316,18739));
}
}
python:https://github.com/hamcrest/PyHamcrest
from hamcrest import *
import unittest
class BiscuitTest(unittest.TestCase):
def testEquals(self):
theBiscuit = Biscuit('Ginger')
myBiscuit = Biscuit('Ginger')
assert_that(theBiscuit, equal_to(myBiscuit))
if __name__ == '__main__':
unittest.main()
assert_that(theBiscuit, equal_to(myBiscuit))
真实结果,期待结果
1'25
https://static.javadoc.io/io.rest-assured/xml-path/3.3.0/io/restassured/path/xml/XmlPath.html
特有:
To get the number of categories with type attribute equal to 'groceries':
int items = with(XML).get("shopping.category.findAll { it.@type == 'groceries' }.size()");
Get the chocolate price:
int priceOfChocolate = with(XML).getInt("**.find { it.name == 'Chocolate' }.price"
用python创建一个简单的http服务
https://www.jianshu.com/p/94731886b28e
端口为8329
对于数量的断言,json也可用 .size()
//断言category的数量为3
.body("shopping.category.size()",equalTo(3))
//断言category中type为supplies的数量为1
.body("shopping.category.find{it.@type=='supplies'}.size()",equalTo(1))
it代表类目下
//断言category中type为supplies的第一个item中的price为5
.body("shopping.category.find{it.@type=='supplies'}.item[0].price",equalTo("5"))
package restassured;
import org.junit.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.core.IsEqual.equalTo;
public class Xml_demo {
@Test
public void testXML(){
given().
get("http://localhost:8329/demo.xml")
.then()
//断言category的数量为3
.body("shopping.category.size()",equalTo(3))
//断言category中type为supplies的数量为1
.body("shopping.category.find{it.@type=='supplies'}.size()",equalTo(1))
//断言category中type为supplies的第一个item中的price为5
.body("shopping.category.find{it.@type=='supplies'}.item[0].price",equalTo("5"));
}
}
1‘40
用例调试:log、proxy
get("http://localhost:8329/demo.xml").prettyPeek()
.log().all() 可以打印全部请求
package restassured;
import org.junit.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.core.IsEqual.equalTo;
public class Xml_demo {
@Test
public void testXML(){
given().log().all()
.get("http://localhost:8329/demo.xml").prettyPeek()
.then()
//断言category的数量为3
.body("shopping.category.size()",equalTo(3))
//断言category中type为supplies的数量为1
.body("shopping.category.find{it.@type=='supplies'}.size()",equalTo(1))
//断言category中type为supplies的第一个item中的price为5
.body("shopping.category.find{it.@type=='supplies'}.item[0].price",equalTo("5"));
}
}
使用proxy进行调试 请求时发现异常,走代理进行调试
package restassured;
import org.junit.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.core.IsEqual.equalTo;
public class Xml_demo {
@Test
public void testXML(){
given().log().all()
//配置代理
.proxy("10.231.21.240",8888)
.get("http://localhost:8329/demo.xml").prettyPeek()
.then()
//断言category的数量为3
.body("shopping.category.size()",equalTo(3))
//断言category中type为supplies的数量为1
.body("shopping.category.find{it.@type=='supplies'}.size()",equalTo(1))
//断言category中type为supplies的第一个item中的price为5
.body("shopping.category.find{it.@type=='supplies'}.item[0].price",equalTo("5"));
}
}
Charles中可成功抓取到请求数据
❖ 断言体系
导出response中的值
.extract().response()
package restassured;
import io.restassured.response.Response;
import org.junit.BeforeClass;
import org.junit.Test;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertEquals;
public class Json_Testerhome {
@BeforeClass
public static void BeforeClass(){
useRelaxedHTTPSValidation();
}
@Test
public void topic(){
Response response = given()
.when()
.get("https://testerhome.com/api/v3/topics.json").prettyPeek()
.then()
//获取第一个topic的id
// .body("topics.id[0]",equalTo(18739))
//获取最后一个topic的user的login信息
// .body("topics[-1].user.login",equalTo("cmlanche-github"))
//获取id为18316的topic的title
// .body("topics.find{it.id==18316}.title",
// equalTo("[深圳] 微众银行内部人才招聘高级测试工程师 / 测试工程师")) //页面修改
//hasItems实例
.body("topics.id",hasItems(18316,18739))
.extract().response();
String id1=response.path("topics.id[0]");
String id2=response.path("topics.id[1]");
assertEquals(id1,id2);
}
}
**.find { it.name == 'Chocolate' }.price
package restassured;
import org.junit.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.core.IsEqual.equalTo;
public class Xml_demo {
@Test
public void testXML(){
given().log().all()
//配置代理
.proxy("10.231.21.240",8888)
.get("http://localhost:8329/demo.xml").prettyPeek()
.then()
//断言category的数量为3
.body("shopping.category.size()",equalTo(3))
//断言category中type为supplies的数量为1
.body("shopping.category.find{it.@type=='supplies'}.size()",equalTo(1))
//断言category中type为supplies的第一个item中的price为5
.body("**.find{it.@type=='supplies'}.item[0].price",equalTo("5"));
}
}
package restassured;
import org.junit.BeforeClass;
import org.junit.Test;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.greaterThan;
public class Json_Testerhome_hw {
@BeforeClass
public static void BeforeClass(){
useRelaxedHTTPSValidation();
}
@Test
public void topic(){
given()
.when()
.get("https://testerhome.com/api/v3/topics.json")
.then()
.body("topics.findAll{it.excellent==1}.size()",greaterThan(4));
}
}
Groovy语法中的findAll 与 find
https://www.w3cschool.cn/groovy/groovy_closures.html
业务测试:
方法一:
将登陆写在@BeforeClass中
后面的方法直接调用登陆信息即可
方法二:
多个接口互相调用
封装成API直接调用
多次调用需要去被抽象成标准的函数,在方法内被调用即可
package restassured;
import io.restassured.response.Response;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.useRelaxedHTTPSValidation;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
public class Json_Testerhome {
@BeforeClass
public static void BeforeClass(){
useRelaxedHTTPSValidation();
}
public void topic(){
Response response = given()
.when()
.get("https://testerhome.com/api/v3/topics.json").prettyPeek()
.then()
//获取第一个topic的id
// .body("topics.id[0]",equalTo(18739))
//获取最后一个topic的user的login信息
// .body("topics[-1].user.login",equalTo("cmlanche-github"))
//获取id为18316的topic的title
// .body("topics.find{it.id==18316}.title",
// equalTo("[深圳] 微众银行内部人才招聘高级测试工程师 / 测试工程师")) //页面修改
//hasItems实例
.body("topics.id",hasItems(18316,18739))
.extract().response();
String id1=response.path("topics.id[0]");
String id2=response.path("topics.id[1]");
assertEquals(id1,id2);
}
@Test
public void baidu() {
topic(); //此处直接调用即可
given()
.log().all()
.queryParam("wd", "mp3")
.when()
.get("http://www.baidu.com/s")
.then()
.log().all()
.statusCode(200)
.body("html.head.title", equalTo("mp3_百度搜索"))
.time(lessThan(2L), TimeUnit.SECONDS);
}
}
Json schema