Abstract
本文首先介绍了服务端开放API的交互参数和返回格式,引出测试这样风格的API所需要注意的点,进而介绍如何使用Cucumber结合开源的Rest-Assured来测试开放API。对JSON格式的返回数据,本文介绍了如何使用JSON Schema来做数据结构和数据有效性验证,从而保证即使在复杂、大量返回数据的情况下也能够轻松地验证数据结构是否符合期望,同时不放过任何一个不合法的字段值。
服务端开放API
服务端开放API简介
所谓的开放API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,所开放的API就被称作OpenAPI(开放API)
介绍本例中所使用的API功能,参数和返回结构
本文中所涉及到的开放API主要实现了对数据的查询:根据卡号查询银行卡信息、分页获取满足条件的银行卡记录、查询银行卡对应的账单地址信息等。读者可以通过类io.cucumber.samples.dw.controller.AppController
所提供的各个方法去逐个查看。后续行文中主要使用了一个开放API:/card/query
Sample app 的实现和启动方式
本文中所述被测试应用是基于Spring boot实现,采用Spring boot可以加快开发和部署速度,加上Spring boot的快速启动方式,能够在开发环境中迅速启动并验证功能的实现是否符合预期设计。
功能实现
被测应用在功能上主要模拟现实的银行业务场景,简单实现了银行卡及其持卡人的信息管理。为了切合当前我们所介绍的测试方法,在实现业务的过程中主要关注了银行卡、持卡人信息的查询。对于行文中所采用的被测Open API,下文会给出详细的解释。
系统略图
在实现开放API功能的过程中,本文主要采用了如图 1 所示的架构,并使用主流的工具加以实现。
应用启动
AppStarter类是整个应用的启动点,启动过程中主要做了:
1, 初始化Spring;
2, 启动Spring boot 及其内嵌的Web Container以接收HTTP请求;
3, 为了便于读者搭建环境,减少不必要的数据库配置过程,同时也为了保证每次运行的时候都是一个干净的环境,数据库采用的是Derby的Memory模式,这样,在Sample app每次启动时都需要重新初始化数据库,并写入测试数据。
当应用成功启动之后,可以从控制台看到类似如下的输出信息
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: 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
时,能够看到如下的输出内容:
[
{
"cardCount": 6
},
{
"addressCount":