之所以选择基于 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 的常用方法有如下两种:
以上两种方式的效果是等价的,读者可以依据实际情况采用不同的实现方式。
截止到这里,读者应该已经能够成功搭建出基于 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 来做数据结构和数据有效性验证,从而保证即使在复杂、大量返回数据的情况下也能够轻松地验证数据结构是否符合期望。
来源: http://www.ibm.com/developerworks/cn/cloud/library/cl-python-openwhisk-bluemix-trs/index.html