一般而言,对于 OpenAPI 的测试通常采用 Google Chrome 的 PostMan 插件或者采用 Firefox 的 Poster 插件来手工调用,这样的测试方式对于简单的功能验证是可行的,但是对于返回值的验证也仅限于肉眼观察,对于返回内容的格式、数据类型等验证方面却有明显的不足。本文介绍了一种基于 Cucumber 和 RestAssured 来验证 OpenAPI 的方式,不但能够让参数变化一目了然,更能提供有效的返回值验证手段。关于 Cucumber 的认识和初级使用,请详见文章《Cucumber使用进阶》。
所谓的开放 API 是服务型网站一种常见的应用,网站的服务商将自己的网站服务封装成一系列 API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,所开放的API就被称作 OpenAPI(开放API)。
本文中所涉及到的开放 API 主要实现了对数据的查询:根据卡号查询银行卡信息、分页获取满足条件的银行卡记录、查询银行卡对应的账单地址信息等。读者可以通过类 io.cucumber.samples.dw.controller.AppController 所提供的各个方法去逐个查看。后续行文中主要使用了一个开放 API:“/card/query”。
本文中所述被测试应用是基于 Spring boot 实现,采用 Spring boot 可以加快开发和部署速度,加上 Spring boot 的快速启动方式,能够在开发环境中迅速启动并验证功能的实现是否符合预期设计。
被测应用(样例应用)在功能上主要模拟现实的银行业务场景,简单实现了银行卡及其持卡人的信息管理。为了切合当前我们所介绍的测试方法,在实现业务的过程中主要关注了银行卡、持卡人信息的查询。对于文中所采用的被测 Open API,下文会给出详细的解释。
在实现开放 API 功能的过程中,本文主要采用了如图 1 所示的架构,并使用主流的工具加以实现。
系统基于 Spring-boot 结合 Spring-context 和 Spring-jdbc 开发而实现,在其上,搭建了提供 REST 服务的 Controller,并在系统启动时,使用 AppStarter 初始化数据和清理环境。
AppStarter 类是整个应用的启动点,启动过程中主要做了:
当应用成功启动之后,可以从控制台看到类似如下的输出信息:
. ____ _ __ _ _
/// / ___'_ __ _ _(_)_ __ __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
/// ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_/__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.2.RELEASE)
… … … … … …
/*** Database initialization ***/
CREATE TABLE CARD
(
ID INT PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY ( START WITH 1, INCREMENT BY 1),
CARD_NUM CHAR(8) NOT NULL UNIQUE,
IS_PRIMARY_CARD SMALLINT DEFAULT 0 NOT NULL,
CARD_OWNER_NAME VARCHAR(64) NOT NULL,
CARD_TYPE SMALLINT DEFAULT 0 NOT NULL,
STAR_POINTS DECIMAL(10) DEFAULT 0.00 NOT NULL
)
INSERT INTO CARD (CARD_NUM, IS_PRIMARY_CARD, CARD_OWNER_NAME, CARD_TYPE, STAR_POINTS)
VALUES ('C0000001', 1, 'CENT LUI', 0, 1024.64)
INSERT INTO CARD (CARD_NUM, IS_PRIMARY_CARD, CARD_OWNER_NAME, CARD_TYPE, STAR_POINTS)
VALUES ('C0000002', 1, 'ROD JOHN', 0, 1048576.16)
INSERT INTO CARD (CARD_NUM, IS_PRIMARY_CARD, CARD_OWNER_NAME, CARD_TYPE, STAR_POINTS)
VALUES ('C0000003', 1, 'STEVE JOBS', 0, 1048576.16)
INSERT INTO CARD (CARD_NUM, IS_PRIMARY_CARD, CARD_OWNER_NAME, CARD_TYPE, STAR_POINTS)
VALUES ('S0000001', 0, 'CENT LUI', 1, 0.00)
INSERT INTO CARD (CARD_NUM, IS_PRIMARY_CARD, CARD_OWNER_NAME, CARD_TYPE, STAR_POINTS)
VALUES ('S0000002', 0, 'ROD JOHN', 1, 512.64)
INSERT INTO CARD (CARD_NUM, IS_PRIMARY_CARD, CARD_OWNER_NAME, CARD_TYPE, STAR_POINTS)
VALUES ('S0000003', 0, 'STEVE JOBS', 1, 1024.64)
CREATE TABLE ADDRESS
(
ID INT PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY ( START WITH 1, INCREMENT BY 1),
CARD_NUM CHAR(8) NOT NULL,
REGION VARCHAR(128) NOT NULL,
COUNTRY VARCHAR(6) NOT NULL DEFAULT 'CHN',
STATE VARCHAR(64) NOT NULL,
CITY VARCHAR(64) NOT NULL,
STREET VARCHAR(64) NOT NULL,
EXT_DETAIL VARCHAR(128) NOT NULL,
FOREIGN KEY (CARD_NUM) REFERENCES CARD (CARD_NUM)
)
INSERT INTO ADDRESS (CARD_NUM, REGION, COUNTRY, STATE, CITY, STREET, EXT_DETAIL)
SELECT
CARD_NUM,
'AP',
'CN',
'HeNan',
'LuoYang',
'Peking Rd',
'Apartment 1-13-01 No.777'
FROM CARD
INSERT INTO ADDRESS (CARD_NUM, REGION, COUNTRY, STATE, CITY, STREET, EXT_DETAIL)
SELECT
CARD_NUM,
'EU',
'ES',
'Madrid',
'Sol',
'Century Rd',
'Apartment 1-13-01 No.777'
FROM CARD
… … … … … …
/*** Open API URI mappings ***/
Mapped "{[/address/count],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.countAddress(java.lang.String)
Mapped "{[/card/count],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.countCards(java.lang.String)
Mapped "{[/address/query],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.getAddressByCardNum(java.lang.String)
Mapped "{[/address/all],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.getAllAddress()
Mapped "{[/address/paged],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.getPagedAddress(java.lang.Integer,java.lang.Integer)
Mapped "{[/card/query],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.getCardByCardNum(java.lang.String)
Mapped "{[/card/all],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.getAllCards()
Mapped "{[/card/paged],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<io.cucumber.samples.dw.base.StandardJsonResponse> io.cucumber.samples.dw.controller.AppController.getPagedCards(java.lang.Integer,java.lang.Integer)
Mapped "{[/database/test],methods=[GET],produces=[application/json]}" onto org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Integer>> io.cucumber.samples.dw.controller.DatabaseController.filterAddressByCardNum()
… … … … … …
Tomcat started on port(s): 8080 (http)
当应用启动之后,可以通过应用提供的 Open API/database/test 来验证系统是否正确启动。若系统正确启动了所有的模块,那么当以 GET 方法访问 Open API/database/test 时,能够看到如 清单 2 的返回内容:
[
{
"cardCount": 6
},
{
"addressCount": 12
}
]
在本例中,这个开放 API 提供了根据卡号查询银行卡信息以及对应账单地址信息的功能,通过查看它的实现代码(清单 3),可以发现它接收一个 String 类型的参数"cardNum";其返回值是一个银行卡列表所序列化之后的 JSON 数据。
@RequestMapping(value = "/card/query", method = RequestMethod.GET,
produces = "application/json")
@ResponseBody
ResponseEntity<StandardJsonResponse> getCardByCardNum(
@RequestParam("cardNum") String cardNum) {
StandardJsonResponse<List<Card>> response = new StandardJsonResponse<>();
List<Card> cardList = cardService.queryCardsByCardNum(cardNum);
response.setData(cardList);
ResponseEntity<StandardJsonResponse> entity =
new ResponseEntity(response, HttpStatus.OK);
return entity;
}
在一次测试调用中,该 Open API 返回了如 清单 4 所示的测试内容:
{
"errName": null,
"errMsg": "SUCCESS",
"errCode": 0,
"data": [
{
"id": 5,
"cardNum": "S0000002",
"cardOwnerName": "ROD JOHN",
"cardType": "1",
"cardSeqNum": 0,
"starPoints": 512,
"cardBillingAddressList": [
{
"id": 5,
"cardNum": "S0000002",
"region": "AP",
"country": "CN",
"state": "HeNan",
"city": "LuoYang",
"street": "Peking Rd",
"extDetail": "Apartment 1-13-01 No.777"
},
{
"id": 11,
"cardNum": "S0000002",
"region": "EU",
"country": "ES",
"state": "Madrid",
"city": "Sol",
"street": "Century Rd",
"extDetail": "Apartment 1-13-01 No.777"
}
],
"primaryCard": false
}
]
}
通过以上对 Open API 的简介和本例中所主要使用的 API“/card/query” 的介绍,相信读者对于样例应用及其功能有了较为清晰的理解。如果一个测试人员拿到了一个对这样的 Open API 的测试需求,他会如何检查这个 API 是否能提供卡片信息查询功能呢?
作为测试人员,在面对这个 Open API 测试需求时,通常需要弄明白以下的问题:
诚然,对于真实情况下的 Open API,除去正向的功能性验证之外还有很多其他需要验证的点,例如非功能性验证内容和异常验证相关的部分。这里我们暂时先不考虑这些方面。
其实,上述 5 个问题的答案已经在前面有所介绍了,手动验证该 API,获取上述问题的答案也不是不可完成的任务,但是手动验证却也有其局限性,尤其是有大量待验证的 API 的情形下。通常,在解决了 API 能否被被验证的问题之后,所面临的是如何能以一种简单而且行之有效的方式自动化的验证这些 API。
现实工作中,由于开发迭代速度快,产品代码变更速度快、幅度大,更会出现 API 文档更新不及时甚至没有文档的情况。基于解决这些问题的考虑,我们首先想到了 Live documentation,进而想到了基于 Cucumber 来实现自动化验证 Open API。
之所以选择基于 Cucumber 来测试 Open API 主要是因为:
接下来,我们将以 step-by-step 的方式讲述如何实现基于 Cucumber 测试 Open API。文中涉及到的功能实现要求实验机器必须预装 Java7 和 Maven3.2,对于如何安装 Java7 和 Maven3.2 以及对应的环境设置,已经超出了本文范畴,如感兴趣,可以参考 Oracle JDK 和 Apache Maven 的相关文档。
首先,从创建 Maven 项目(Intellij IDEA 下称作模块)开始:
创建 Maven 项目可以采用可视化的方式来做:Eclipse 和 Intellij IDEA 都已经充分支持,且有详细的创建过程描述。本文采用命令行方式来创建 Maven 项目,待配置好 JDK 和 Maven 环境之后,在命令行下执行 清单 5 所示命令,可以创建一个 Maven 项目:
mvn -B archetype:generate -DgroupId=io.cucumber.samples.dw -DartifactId=open-api-test -Dversion=1.0.0-SNAPSHOT
其次,添加 project dependencies。完整的代码清单,查看清单 6。
待上述命令执行完成之后,在命令执行的目录下,会发现有一个新建的项目"open-api-test"。进入该项目,打开 pom.xml,添加所需要的 dependencies,此处将测试所需要的所有 dependencies 都添加进来,后续行文中涉及到工具或功能本身时,会对对应的 dependencies 加以介绍。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.cucumber.samples.dw</groupId>
<artifactId>open-api-test</artifactId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<name>open-api-test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
<maven.compiler.version>3.3</maven.compiler.version>
<junit.version>4.12</junit.version>
<cucumber.version>1.2.4</cucumber.version>
<spring.version>4.1.7.RELEASE</spring.version>
<restassured.version>2.9.0</restassured.version>
<jodaTime.version>2.9.3</jodaTime.version>
</properties>
<dependencies>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>rest-assured</artifactId>
<version>${restassured.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>${restassured.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<encoding>UTF-8</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Werror</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
做完上述工作之后,可以通过执行 清单7 所示命令下载对应dependencies到本地,并验证配置的正确性。
mvn –U clean compile
最后,启用 Cucumber。
启用 Cucumber 可以有很多层次,上述步骤中添加了 Cucumber-JVM 到 Maven 的项目依赖中,也是一种启用。
对于致力于以 Cucumber 实现 Live Documentation 的读者来说,本文建议是用 Cucumber 高阶的功能:DI(Dependency Injection)容器。是的,这里所说的 Dependency Injection 就是大家在使用 Spring Framework 入门所学习的那个 DI。
DI 容器可以帮我们管理类实例的创建和维护,使得我们在使用类的时候不需要再使用 new 关键字去创建它,并且,DI 容器可以按照指定的生命周期去管理类的实例,这样,我们在设计和实现测试用例时,能够更加关注架构和业务。
Cucumber 几乎支持所有主流的 DI 容器:
本文采用 Spring 作为 DI 容器,原因是 Spring Framework 在 Java 开发者中使用极其广泛,对于 Spring DI 的用法,有比较大的群众基础。
将 Spring 与 Cucumber 集成的过程其实相当简单,Cucumber-JVM 提供了一个 cucumber-spring 模块工具,管理 Cucumber steps、helper utilities 的创建,并注入到对应的测试场景中。上述 Maven 项目的 dependencies 中的如清单 8,是用于 Cucumber 和 Spring 集成的:
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
其中"spring-context"部分可以有不同的表现形式,可以以上述 dependency 的形式出现,也可以用 Spring annotation 配置的形式出现,如清单 9 所示 java snippet 中的 ContextConfiguration annotation:
@ContextConfiguration("classpath:cucumber.xml")
public class AppStarterTest {
}
以 Spring 作为 DI 容器,还需要给出 Spring beans/context 的配置文件,cucumber-spring 将其定义为“cucumber.xml”,关于“cucumber.xml”中的内容,其符合 Spring 定义规范,其中你可以定义各种 bean, 引用各种 properties,定义需要 scan 的 packages,配置是否采用 annotation。
下面是本文采用的一个样例,采用了 annotation config 方式,因此在 cucumber.xml 需要的定义非常简单。如清单 10 所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<context:annotation-config/>
<context:component-scan base-package="io.cucumber.samples.dw.helpers"/>
</beans>
既然 DI 容器中包含了测试用例运行所需要的各种要素,那么何时初始化各种资源是至关重要的。在解释容器初始化之前,本文为您推荐一个较为实用的测试用例组织结构:如 图 2 所示:
之所以采用这样的组织结构在于能够:
那么 DI 容器应该在什么时候初始化呢?
首先,使用 helpers 初始化 DI 容器肯定是不合适的,因为 helper 类和方法自身不能够描述场景,他们只是场景中的一个部分;
其次,如果采用 steps 类来初始化 DI 容器,会造成一个问题:对于有多个 steps 类的情况,会造成 DI 容器初始化多次!
最后,也是最合适的地方:cases package 下的测试用例入口点,其中使用了@ContextConfiguration("classpath:cucumber.xml")出使初始化了 DI 容器。 清单11 为 DI 容器初始化的一个样例:
package io.cucumber.samples.dw.cases;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
/**
* Created by stlv for developerworks article
*/
@RunWith(Cucumber.class)
@CucumberOptions(
format = {
"pretty",
"html:target/html-report/",
"json:target/json-report/dw.json"
},
features = {
"classpath:features"
},
glue = {
"io.cucumber.samples.dw.steps"
},
tags = {
"@api",
"~@ui"
}
)
@ContextConfiguration("classpath:cucumber.xml")
public class AppStarterTest {
}
对于 CucumberOptions,请参考Cucumber 使用进阶文章中的介绍,此处不再赘述。
为什么要 override 已经定义好的 CucumberOptions?最常见的情况可能是这样的:
因此,能够掌握 override CucumberOptions,对于熟练掌握 Cucumber 是非常有益处的。Override CucumberOptions 的常用方法有如下两种:
将 cucumber.options 直接传递给 Java 命令,例如:
java -Dcucumber.options="-g step_definitions features" cucumber.api.cli.Main
将 cucumber.options 传递给 Maven 命令,例如:
mvn test -Dcucumber.options="-g step_definitions features"
通过定义环境变量 CUCUMBER_OPTIONS 来实现:
export CUCUMBER_OPTIONS="-g step_definitions features"
以上两种方式的效果是等价的,读者可以依据实际情况采用不同的实现方式。
截止到这里,读者应该已经能够成功搭建出基于 Spring DI 容器的自动化用例测试工程了,能够以 Live documentation 方式来做测试。
但是,对于本文中待测的 Open API,它返回的是 JSON 数据,因此,并不建议读者止步于此,建议读者继续阅读下文,了解 Rest-Assured 工具,将其集成到测试环境,以便实现 JSON Schema 验证。
JSON Schema 是一个非常强大的 JSON 结构验证工具,它通过定义 JSON 的结构、数据取值范围等方式验证 JSON 数据。
JSON Schema 将 JSON 数据类型划分为 6 种类型:
另外,加之 JSON Schema 也支持"引用"的概念,实现 Schema 定义的复用,因此,使用上述常用类型通过各种组合,就可以定义出各种复杂的数据类型。
本文所述的 Open API 的返回值也是 JSON 数据,样例如下 snippet 所示。从中可以看出,返回的 JSON 数据最外层是一个通用的结构,用于表示本次 API 调用结果;然后,"data"property 是 API 调用所返回的业务数据。在本例中,它是一个卡片信息描述数据,包括了"id","cardNum"等诸多 properties。同时,还包含了一个"cardBillingAddressList"用于标识持卡人的账单地址信息列表。
{
"errName": null,
"errMsg": "SUCCESS",
"errCode": 0,
"data": [
{
"id": 1,
"cardNum": "C0000001",
"cardOwnerName": "CENT LUI",
"cardType": "0",
"cardSeqNum": 0,
"starPoints": 1024,
"cardBillingAddressList": [
{
"id": 1,
"cardNum": "C0000001",
"region": "AP",
"country": "CN",
"state": "HeNan",
"city": "LuoYang",
"street": "Peking Rd",
"extDetail": "Apartment 1-13-01 No.777"
},
{
"id": 7,
"cardNum": "C0000001",
"region": "EU",
"country": "ES",
"state": "Madrid",
"city": "Sol",
"street": "Century Rd",
"extDetail": "Apartment 1-13-01 No.777"
}
],
"primaryCard": true
}
]
}
对于这样的返回值,根据上面所述的 JSON Schema 知识,定义出的 Schema 信息如下:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "银行卡数据格式验证 Schema",
"definitions": {
"eleInnerData": {
"properties": {
"id": {
"type": "integer",
"minimum": 1
},
"cardNum": {
"$ref":"common-schema.json#/definitions/cardNum"
},
"cardOwnerName": {
"type": "string",
"minLength": 2,
"maxLength": 128
},
"cardType": {
"type": "string",
"minLength": 1,
"maxLength": 1,
"enum": [
"0",
"1"
]
},
"cardSeqNum": {
"type": "integer",
"minimum": 0,
"maximum": 127
},
"starPoints": {
"type": "number",
"minimum": 0.00
},
"cardBillingAddressList": {
"$ref": "address-schema.json"
},
"primaryCard": {
"type": "boolean",
"enum": [
true,
false
]
}
},
"required": [
"id",
"cardNum",
"cardOwnerName",
"cardType",
"cardSeqNum",
"starPoints",
"cardBillingAddressList",
"primaryCard"
],
"additionalProperties": false
},
"eleData": {
"type": "array",
"items": {
"$ref": "#/definitions/eleInnerData"
},
"minItems": 0
}
},
"allOf": [
{
"$ref": "common-schema.json"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/eleData"
}
},
"required": [
"data"
],
"additionalProperties": true
}
]
}
其中引用了 common-schema 的定义如下:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "通用交互数据格式验证 Schema",
"definitions": {
"cardNum": {
"type": "string",
"minLength": 8,
"maxLength": 8,
"pattern": "[C|S](0*)//d+"
},
"errName": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"errMsg": {
"type": "string",
"minLength": 1
},
"errCode": {
"type": "integer",
"maximum": 0
}
},
"type": "object",
"properties": {
"errName": {
"$ref": "#/definitions/errName"
},
"errMsg": {
"$ref": "#/definitions/errMsg"
},
"errCode": {
"$ref": "#/definitions/errCode"
}
},
"required": ["errName","errMsg","errCode"],
"additionalProperties": true
}
本文对于 JSON Schema 的定义并未详细描述,读者可以参考 Understanding JSON Schema 学习如何定义一个有效的 JSON Schema。
Rest-Assured 从 version 2.10 开始支持 JSON Schema Validation,读者只需要在 pom 文件中添加如下的 dependency 就可以支持 JSON Schema Validation 了:
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.9.0</version>
<scope>test</scope>
</dependency>
使用 Rest-Assured 提供的 Schema Validator 验证 Rest-Assured Response 返回数据是非常简单的,下面这个例子中,只是一行代码就能实现以 schemaFile 所指定的 JSON Schema 来验证 response 的 body。
public void assertThatRepliedCardDataMetSchemaDefinedSpecs(String schemaFile) {
response.body(JsonSchemaValidator.
matchesJsonSchemaInClasspath("schemas/" + schemaFile));
}
本文首先介绍了服务端开放 API 的交互参数和返回格式,进而介绍如何使用 Cucumber 结合开源的 Rest-Assured 来测试开放 API。本文介绍了如何使用 JSON Schema 来做数据结构和数据有效性验证,从而保证即使在复杂、大量返回数据的情况下也能够轻松地验证数据结构是否符合期望。