小明同学所在的敏捷研发团队开发的机票销售系统,需要支持不同客户端对系统的访问,例如手机 App、网页应用等。为了满足对不同客户端访问的支持,研发团队根据需要选择了 Restful Webservice 技术实现对外调用的 API 接口。举例来说,如果客户在手机 App 或网页应用上购买一张机票,那么就需要机票销售系统获得航班的详细信息,另外在付款时,付款处理系统也需要获取航班信息。因此这时就需要一个提供查询航班详细信息的 Restful API 接口。
查询航班详细信息接口的用户故事场景如下:
特性:查询指定航班详细信息。
场景:使用航班号查询航班。
假设 我需要获得航班号为 MU5693 的航班详细信息。
当 我使用航班号MU5693查询航班详细信息
那么 我可以获得如下航班信息:
flightNumber | departure | destination | time |
---|---|---|---|
MU5693 | 首都国际机场T2 | 虹桥国际机场T2 | 08:05 |
虽然我们要实现的是一个 Web Service 接口,但是这个接口的业务意义和逻辑最好让团队中相关的人都能理解,这样才能提高团队的沟通水平和协同效率。如果团队成员中一些人对 JSON 数据或 XML 数据不是太熟悉,我们在设计 Web Service 服务时,可以先定义 Web Service 服务接口的输入参数和返回信息,这样可以让尽可能多的人理解我们所设计的 Web Service 服务。
根据上面的用户故事场景描述,我们可以先用 Java 和 Cucumber 实现查询航班详细信息的 Web Service 的自动化测试脚本。当测试脚本用做 BDD(行为驱动开发)时,测试脚本的编写人员和客户端 App 的开发人员为同一个人或团队,测试脚本中使用的类正好是客户端 App 开发人员要实现的类,这样可以通过编写测试脚本梳理好客户端 App 类的设计特点。
航班详细信息 Java 实体类,如下:
public class Flight {
private String flightNumber;
private Airport departure;
private Airport destination;
private String time;
public Flight(String flightNumber, Airport departure, Airport destination, String time) {
this.flightNumber = flightNumber;
this.departure = departure;
this.destination = destination;
this.time = time;
}
public String getFlightNumber() {
return flightNumber;
}
public Airport getDeparture() {
return departure;
}
public Airport getDestination() {
return destination;
}
public String getTime() {
return time;
}
}
Web Service 接口返回的航班信息测试代码如下:
public class FlightDetailsSteps {
private String flightNumber; //航班号
private Flight matchingFlight; //符合查询结果的航班
@Given("^我需要获得航班号为(.*)的航班详细信息$")
public void flight_number(String flightNumber) throws Throwable {
this.flightNumber = flightNumber;
}
@When("^我使用航班号查询航班详细信息$")
public void request_flight_details() throws Throwable {
FlightStatusClient client = new FlightStatusClient();
matchingFlight = client.findByFlightNumber(flightNumber); //我们用client类实现对Restful WebService的访问
}
@Then("^我可以获得如下航班信息:$")
public void verify_details(DataTable flightDetails) throws Throwable {
flightDetails.diff(newArrayList(matchingFlight));
}
}
以上这段自动化测试代码相对简单,我们使用 FlightStatusClient 这个类来访问 Web Service 获得我们想要的航班信息。
为了测试 WebService 返回的航班详细信息是否正确,我们使用 Cucumber DataTable 类从特性文件(例如 FlightDetails.feature)中读取测试场景的数据用例:
flightNumber | departure | destination | time |
---|---|---|---|
MU5693 | 首都国际机场T2 | 虹桥国际机场T2 | 08:05 |
使用以上这个数据表格时,第1行表格头为字段名称,第2行以下为字段值。 比较从测试场景中读取测试用例数据和通过 FlightStatusClient 从 Web Service 接口获取的航班明细数据,就可以验证 FlightStatusClient 这个应用客户端类是否可以通过测试。
假设航班详细信息的 Web Service 地址为:
http://www.qatools.cn/demon/api/rest/flights
我们可以从服务配置中心或配置文件中获取以上地址,访问航班详细信息客户端 App 示例代码如下:
public class FlightStatusClient {
//Web Service 地址
@Value("SERVICE_URL")
private String serviceURL ;
public Flight findByFlightNumber(String flightNumber) {
//创建Web Service 客户端
Client client = ClientBuilder.newClient();
//指定调用资源地址
WebTarget webTarget = client.target(serviceURL).path(flightNumber);
//获取航班信息并封装为Flight对象
return webTarget.request().buildGet().invoke(Flight.class);
}
}
以上我们讲述的是作为客户端 App 应用测试 Restful Web Service 接口的一个例子。如果作为服务端的敏捷研发团队,该如何测试自己开发的接口呢?对于服务端敏捷团队的自我接口测试,我们主要验证接口是否满足敏捷故事场景的调用要求,对用户(客户端)可能调用的场景进行覆盖。
Rest-Assured
可以支持发起POST、GET、PUT、DELETE、OPTIONS、PATCH 和 HEAD 请求,并且可以很容易的验证和校对这些请求的响应信息。我们在接口测试演示中选择使用 Rest-Assured
对 Restful WebService 接口进行测试。
使用 Rest-Assured
进行 Restfull API 测试,使用 Maven 作为编译构建工具时,需要先引用相关依赖包,Maven 工程文件 pom.xml 文件配置步骤如下。
Rest-Assrued
Maven 工程的 pom.xml 文档中添加如下代码:
io.rest-assured
rest-assured
3.1.0
test
说明一下:
在 Maven 的 pom.xml 文件中,Rest-assured
的依赖应该放在 JUnit 依赖声明之前,以确保 Rest-assured
可以引用到正确的 Hamcrest 版本。
Rest-assured 中 JsonPath 和 XmlPath 可以根据配置作为依赖自动传递注入进去。
JsonPath
我们可以使用 JsonPath 轻松的解析 JSON 文档,JsonPath 的实现使用的是 Groovy's GPath 语法,这点容易和 Kalle Stenflo's JsonPath 混淆。
Maven 工程的 pom.xml 文档中添加如下代码:
io.rest-assured
json-path
3.1.0
假设查询到的书籍 JSON 数据如下:
{
"store":{
"book":[
{
"id":"1"
"title":"敏捷测试之:Selenium实战自动化用户验收测试"
"author":"oak du",
"category":"技术",
"price":20.00,
"publisher ": "吐司QA",
"version": "第1版",
},
{
"id":"2"
"title":"愿景驱动开发:如何创建一个小型互联网企业"
"author":"oak du",
"category":"管理",
"price":20.00,
"publisher ": "吐司QA",
"version": "第1版",
},
]
}
}
JsonPath 获得 JSON 数据的代码如下:
//使用UTF-8字符编码
JsonPath.config = new JsonPathConfig("UTF-8");
//获取第1本书书名
String title = JsonPath.with(JSON).get("store.book[0].title");
//获取第1本书作者
String author = JsonPath.with(JSON).get("store.book[0].author");
XmlPath
如果需要解析 XML 文档,我们需要引入 XmlPath 依赖包。
Maven 工程的 pom.xml 文档中添加如下代码:
io.rest-assured
xml-path
3.1.0
假设查询到的书籍 XML 数据如下:
TOOLSQA
TOOLSQA
XmlPath 获取的 XML 数据的代码如下:
//设置UTF-8编码格式
XmlPath.config = new XmlPathConfig("UTF-8");
//获取第1本书籍
//获取书籍名
String title = book.getAttribute("titile");
在正式使用 Rest-assrued
开始我们的敏捷测试故事之前,我们先了解一下 Rest-assured
的常用方法。
1. Response 常用方法:
response.asString()
说明:获取请求返回内容体。
response.getContentType()
说明:获取响应的内容类型。
response.getStatusCode()
说明:获取响应的状态代码。
response.getHeaders()
说明:获取所有响应头信息。
response.getHeader(String name)
说明:根据指定的 Header 名称,获取对应的响应信息。
response.getCookies()
说明:获取所有 Cookies 信息。
response.getTime()
说明:响应时间(单位:毫秒)。
2. 获取 Respose 节点值方法:
response.then().body("returncode",equalTo(0));
说明: returncode 是否等于0。
response.getBody().prettyPrint();
说明:格式化打印 JSON 数据。
given().param("category","管理").param("publisher","吐司QA").get("www.toolsqa.cn");
说明:URL 参数化拼接成 www.toolsqa.cn/category="管理"&publisher="吐司QA"
。
when().get("www.toolsqa.cn/category="管理"&publisher="吐司QA"").then().time(lessThan(100L),TimeUnit.MILLISECONDS);
说明:判断响应时间是否少于预期值。
3. assured 断言数据处理:
get("/store/book").then().body("store.book[0].titile",equalTo("敏捷测试之:Selenium实战自动化用户验收测试"));
说明:判断 store 下面的 book 中 titile 节点是否是“敏捷测试之:Selenium实战自动化用户验收测试”。
get("/store/book").then().body("store.books.titile,hasItems("敏捷测试之:Selenium实战自动化用户验收测试", "愿景驱动开发:如何创建一个网络初创企业"));
说明:store 下面 book 值是否包含书籍“敏捷测试之:Selenium实战自动化用户验收测试”,“愿景驱动开发:如何创建一个网络初创企业”。
given().param("titile,"如何创建一个网络初创企业").then().statusCode(200).body("id",equalTo(2),"titile", containsString("愿景驱动开发:如何创建一个小型互联网企业").when().get("/store/book");
说明:参数 title,当我发送 get 请求之后,那么你给我返回响应码200,并且 id=2,titile为“愿景驱动开发:如何创建一个网络初创企业”。
在初步了解如何使用 Rest-assured
测试 Restful Webservice 之后,我们进入正题,继续小明同学的故事。
在敏捷研发团队对自己的 Web Service 接口测试时,由于用户故事使用场景比较偏向技术,我们可以在测试场景的数据准备中直接使用期望 Restful Web Service 接口返回的 JSON 数据,作为测试验证的预期结果。这样可以提高 JSON 数据的利用效率,也可以让使用的研发人员和测试人员了解被测试接口的 Demon 文档。
客户端用户故事场景改为服务端用户故事场景如下:
特性: 查询指定航班详细信息。
场景: 以 JSON 格式返回指定的航班明细信息。
假设 我需要获得航班号为 MU5693 的航班详细信息。
当 我使用航班号 MU5693 查询航班详细信息。
那么 我可以获得如下航班信息:
{
"flightNumber":"MU5693",
"departure":"首都国际机场T2",
"destination":"虹桥国际机场T2",
"time":" 08:05"
}
首先根据我们的经验,使用 Rest-Assured
时,建议静态导入 Rest-Assured 的相关依赖包,这样可以提高编写速度和运行效率。
io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*
服务端敏捷团队使用 Rest-Assured 编写的 Restful Web Service 接口测试代码如下:
public class FlightDetailsServcieSteps {
private String flightNumber; //航班号
private Flight matchingFlight; //符合查询结果的航班
@Value("SERVICE_URL")
private String serviceURL; //航班详细信息查询WebService 地址
private Sting receivedFlightJsonData;
private String expectedFlightJsonData;
@Given("^我需要获得航班号为(.*)的航班详细信息$")
public void flight_number(String flightNumber) throws Throwable {
this.flightNumber = flightNumber;
}
@When("^我使用航班号查询航班详细信息$")
public void request_flight_details() throws Throwable {
//设置服务器基准地址http://www.qatools.cn/demon/api/rest/
RestAssured.baseURI = this.serviceURL;
//获得服务器响应报文
Response response = given().get("/flights/{"+this.flightNumber+"}");
//转化为JSON数据
receivedFlightJsonData = response.body().asString();
}
@Then("^我可以获得如下航班信息:$")
public void verify_details(String flightDetails) throws Throwable {
//验证Web Service 接口返回的数据是否与期望结果一直
assertJsonEquals(receivedFlightJsonData, expectedFlightJsonData);
}
}
如果客户端团队和服务端团队为了提高协同效率,可以编写一个 JSON 数据的 Schema 文件,例如 flight_schema.json
,客户端和服务端等不同团队可以依此来编写自己的测试数据。Schema 文件建议放在相关人员都可以访问的服务器上,例如 GitLab、SVN,方便相关敏捷研发团队及时获取数据结构的变化。我们也可以使用 Schema 文件,利用 Rest-Assured
验证 WebService 接口返回的 JSON 数据是否符合 Schema 的要求。
使用 Rest-Assured
中 JSON Schema Validation 验证 JSON 数据,首先应导入相关 Maven 工程文件 pom.xml 依赖包:
io.rest-assured
json-schema-validator
3.1.0
test
航班明细信息的 JSON Schema 文件 flight_schema.json
,如下:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title":"flight",
"properties":{
"flightNumber":{
"type":"string",
"description":"航班号"
},
"departure":{
"type":"string",
"description":"出发港"
},
"destination":{
"type":"string",
"description":"到达港"
},
"time":{
"type":"string",
"description":"起飞时间"
}
},
"required":[
"flightNumber",
"departure",
"destination",
"time"
]
}
假设当查询航班号 MU5693 时,返回数据如下:
{
"flightNumber":"MU5693",
"departure":"首都国际机场T2",
"destination":"虹桥国际机场T2",
"time":"08:05"
}
测试 Restful Web Service 接口返回航班数据格式是否正确:
@Value("String SERVICE_RUL")
private String serviceURL;
public void validate_FlightDataFormate(){
get().then( serviceURL).assertThat().body(matchesJsonSchemaInClasspath("flight_schema.json"));
}
更多关于敏捷测试话题 ,请关注微信关注【时牧敏捷社区】