第2章 接口契约分析
接口契约是模块边界最精确的描述。Stub的仿真行为需要完全基于契约定义,而不是基于开发人员对接口的“印象”。本章讲解如何从接口文档中解析出请求结构、响应结构与错误码约定,并形成可被Stub引擎直接消费的契约模型。
2.1 契约分析的核心任务
接口契约分析包含三项关键活动:
- 接口文档解析:将非结构化或半结构化文档(Swagger/OpenAPI、RAML、API Blueprint、WSDL)转换为结构化契约元数据。
- 请求/响应结构提取:明确路径参数、查询参数、请求头、请求体、响应码、响应体Schema、响应头。
- 错误码约定梳理:区分HTTP标准状态码与业务错误码,定义错误响应体格式。
实践要点:契约分析不是一次性工作。接口文档版本更新后,需重新同步Stub的契约定义,建议将文档解析流程自动化。
2.2 接口文档解析——以OpenAPI为例
目前绝大多数RESTful接口使用OpenAPI 3.0规范。以下是一个简化的用户管理接口文档片段:
openapi: "3.0.3"
info:
title: User Service
version: "1.0.0"
paths:
/users/{userId}:
get:
summary: 获取用户信息
parameters:
- name: userId
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
id: { type: integer }
name: { type: string }
email: { type: string }
'404':
description: 用户不存在
content:
application/json:
schema:
type: object
properties:
code: { type: string }
message: { type: string }
解析器需要遍历 paths 节点,提取每个接口的HTTP方法、路径参数、Query参数、Header参数、RequestBody、Response以及与状态码绑定的Schema。解析结果可以持久化为JSON/YAML结构,供Stub生成器使用。
2.3 请求/响应结构分析
从文档中提取的结构必须包含足够信息以模拟完整请求匹配和响应生成。下表列出核心元素:
| 契约元素 | 描述 | Stub匹配示例 |
|---|---|---|
| HTTP Method | GET / POST / PUT / DELETE 等 | "method": "GET" |
| Path Template | 含路径参数占位符,如 /users/{userId} |
"path": "/users/123" |
| Query Parameters | 名称、是否必填、类型、枚举值 | "query": {"page": "1"} |
| Request Headers | 如 Content-Type、Authorization 等 | "headers": {"Authorization": "Bearer *"} |
| Request Body Schema | JSON Schema 定义的结构和约束 | 用于验证请求体并生成响应(可选) |
| Response Status Code | 200, 201, 400, 500 等 | "status": 200 |
| Response Body Schema | 用于生成Stub固定响应或动态响应 | "body": {"id": 1, "name": "Alice"} |
2.4 错误码约定
错误码通常分为两层:
- HTTP状态码:如 400 表示请求格式错误,404 表示资源不存在,500 表示服务内部错误。这部分由协议层约定,Stub直接使用。
- 业务错误码:放在响应体中的
code或errorCode字段。常见的格式包括字符串(如"USER_NOT_FOUND")或整数。业务错误码必须与文档一致,否则Stub返回的模拟错误会被测试误判。
典型错误响应体结构(从OpenAPI提取):
{
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"traceId": "abc-123"
}
在整理错误码约定时,列表记录所有可能状态码与对应业务错误码的映射关系:
| HTTP Status | Business Error Code | Description |
|-------------|---------------------|----------------------|
| 400 | INVALID_PARAM | 请求参数校验失败 |
| 400 | MISSING_HEADER | 缺少必需头字段 |
| 404 | USER_NOT_FOUND | 指定用户不存在 |
| 409 | EMAIL_ALREADY_EXIST | 邮箱已被其他用户占用 |
| 500 | INTERNAL_ERROR | 服务内部异常 |
2.5 从契约到Stub配置
经过上面的分析,可以生成Stub框架能直接加载的配置。以下是一个基于分析的YAML格式Stub规则:
- name: "get-user-200"
request:
method: GET
path: /users/{userId}
headers:
- name: Authorization
pattern: "Bearer .+"
response:
status: 200
headers:
Content-Type: application/json
body: |
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
- name: "get-user-404"
request:
method: GET
path: /users/{userId}
response:
status: 404
body: |
{
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"traceId": "{{UUID}}"
}
注意:一个接口对应 多条 Stub规则(每个状态码一条)。如果文档中
200 和 404 都有Schema,必须为每个状态码生成独立的Stub条目。遗漏错误码场景会导致测试覆盖不足。
2.6 契约分析流程
下图概括了从接口文档到Stub契约的完整分析流程:
该流程图的每个节点都有对应的实现步骤。实际做Stub契约分析时,建议使用工具(如openapi-stub-generator)半自动完成,但手工校验仍是确保准确性的最后防线。
2.7 常见错误与避坑
- 遗漏可选参数:Stub如果使用严格匹配,未在文档中声明的可选参数会导致请求被拒绝。推荐在Stub中对可选参数使用非严格模式或通配。
- 错误码格式不一致:文档中写
"code":"USER_NOT_FOUND",但Stub返回"errorCode": "404",测试断言必然失败。必须建立统一的错误码映射表。 - 响应Schema版本漂移:文档更新后未同步更新Stub配置。建议在CI中增加“文档变更→契约重生成”的流水线。
自动化建议:将OpenAPI文档与Stub配置放在同一仓库,利用
prism或swagger-codegen在构建阶段自动生成Stub规则,减少手工维护成本。