关键要点
- 废弃是过程,不是事件:有效的 API 废弃遵循结构化生命周期——公告、迁移期、强制执行和移除——每个阶段都需要专项测试,确保消费者不会被意外中断。
- 基于标准的信号传递:
Deprecation和SunsetHTTP 头部(RFC 8594)提供了机器可读、可互操作的废弃时间线传达方式,测试套件可以自动检测并断言。 - 版本感知的测试架构:为多个 API 版本维护并行测试套件——而非单一的持续演进套件——使得在迁移窗口期内既能验证旧版本仍然可用,又能开发新版本。
- API 网关简化过渡:API 网关可以集中管理废弃策略——注入废弃头部、对废弃端点进行更严格的限流、将流量路由到新版本——无需修改上游服务。
什么是 API 废弃?
API 废弃是正式宣布某个 API 端点、版本或功能计划移除,并提示消费者迁移到新替代方案的过程。废弃不同于移除:已废弃的 API 仍然可以使用,但它携带的信号表明其生命周期有限。
废弃是 API 生命周期中不可避免的一部分。随着需求演进,API 需要被重新设计、合并或替换为更好的替代方案。挑战在于在不破坏依赖现有 API 的消费者——应用程序、移动客户端、第三方集成——的情况下管理这种演进。
对于测试而言,废弃引入了一系列特定挑战:
- 并行覆盖:测试必须在迁移窗口期内同时覆盖旧(废弃)版本和新版本。
- 信号验证:测试应断言废弃端点返回正确的废弃头部。
- 迁移验证:在废弃版本移除前,测试必须验证消费者已成功完成迁移。
- Sunset 强制执行:在 Sunset 日期后,测试应确认端点返回预期的
410 Gone或404 Not Found。
为什么废弃测试至关重要
无序废弃的代价
没有结构化废弃管理,API 演进就成为高风险活动。常见故障模式:
- 提供方在没有充分通知的情况下移除端点。消费者在生产环境中无警告地中断。
- 迁移期已公告,但消费者没有自动化方式检测到。截止日期过后,消费者仍停留在旧版本。
- 新版本已部署,但旧版本从未移除。API 接口面不断增长,充斥着仍需维护和保障安全的僵尸代码。
2022 年 Twitter API v1 废弃事件影响了数千个未能及时迁移到 v2 的第三方应用。当 v1 关闭时,许多应用无声无息地中断——因为它们的测试套件没有监控废弃信号。而拥有结构化废弃测试的团队提前数月检测到了 Sunset 头部,并按时完成了迁移。
监管与 SLA 影响
许多企业 API 合同包含规定最短废弃通知期的 SLA——通常为 6 到 12 个月。测试这些承诺得到履行(并确保消费者及时收到通知)是在此类协议下运营的团队的 API 治理工作的一部分。
如何在测试中处理 API 废弃
第一步:实施标准废弃头部
IETF 的 RFC 8594 定义了两个用于传达废弃信息的 HTTP 响应头部:
Deprecation:表示资源或 API 已废弃。值为废弃公告的日期。Sunset:表示资源不再可用的日期。
废弃端点的示例响应:
1HTTP/1.1 200 OK
2Content-Type: application/json
3Deprecation: Thu, 01 Jan 2026 00:00:00 GMT
4Sunset: Wed, 01 Jul 2026 00:00:00 GMT
5Link: <https://api.example.com/v2/orders>; rel="successor-version"
6
7{
8 "id": "1234",
9 "status": "shipped"
10}带有 rel="successor-version" 的 Link 头部指向替代端点,使迁移发现成为可编程操作。
第二步:在测试中断言废弃头部
测试套件应明确断言废弃端点返回正确的头部。使用 Newman/Postman:
1// 废弃端点的 Postman 测试脚本
2pm.test("Deprecation 头部存在", function () {
3 pm.response.to.have.header("Deprecation");
4});
5
6pm.test("Sunset 头部存在且在未来", function () {
7 const sunsetHeader = pm.response.headers.get("Sunset");
8 pm.expect(sunsetHeader).to.not.be.null;
9
10 const sunsetDate = new Date(sunsetHeader);
11 const now = new Date();
12 pm.expect(sunsetDate).to.be.above(now,
13 "迁移窗口期内 Sunset 日期必须在未来"
14 );
15});
16
17pm.test("存在 successor-version 链接", function () {
18 const linkHeader = pm.response.headers.get("Link");
19 pm.expect(linkHeader).to.include('rel="successor-version"');
20});在 Sunset 日期后,断言端点不再可用:
1// Sunset 日期后的测试
2pm.test("废弃端点在 Sunset 后返回 410 Gone", function () {
3 pm.response.to.have.status(410);
4});第三步:构建版本感知的测试套件
1flowchart TD
2 subgraph Suite_v1["测试套件:API v1(已废弃)"]
3 V1_func[功能测试\nv1 端点]
4 V1_dep[废弃头部\n断言]
5 V1_compat[向后兼容性\n检查]
6 end
7 subgraph Suite_v2["测试套件:API v2(当前)"]
8 V2_func[功能测试\nv2 端点]
9 V2_new[新功能\n测试]
10 V2_perf[性能\n基准]
11 end
12 subgraph Pipeline["CI 流水线"]
13 Both[迁移窗口期内\n同时运行两套套件]
14 V1_sunset{已过 Sunset\n日期?}
15 Both --> V1_sunset
16 V1_sunset -->|否| Keep[继续运行 v1 套件]
17 V1_sunset -->|是| Remove[移除 v1 套件\n断言 v1 返回 410]
18 end
19
20 Suite_v1 --> Pipeline
21 Suite_v2 --> Pipeline
22
23 style Suite_v1 fill:#fff3e0,stroke:#f57c00
24 style Suite_v2 fill:#e8f5e9,stroke:#388e3c
25 style Remove fill:#ffebee,stroke:#c62828按版本组织测试集合:
1tests/
2├── v1/ # 废弃套件
3│ ├── orders-v1.json # Newman 集合
4│ ├── users-v1.json
5│ └── deprecation-checks.json
6├── v2/ # 当前套件
7│ ├── orders-v2.json
8│ └── users-v2.json
9└── migration/
10 └── parity-checks.json # 验证 v2 与 v1 行为一致
在迁移窗口期内,在 CI 流水线中运行两套套件。在 Sunset 日期后,废弃套件应自动跳过或转换为"预期 404/410"测试。
第四步:迁移一致性测试
在移除废弃版本前,验证新版本对所有消费者用例的功能等价性。一致性测试并行运行两个版本并比较输出:
1// 一致性检查:v1 和 v2 相同请求应返回等价数据
2const v1Response = pm.environment.get("v1_response");
3const v2Response = pm.response.json();
4
5pm.test("v1 和 v2 的订单 ID 匹配", function () {
6 pm.expect(v2Response.id).to.equal(JSON.parse(v1Response).orderId);
7 // 注意:字段从 v1 的 'orderId' 重命名为 v2 的 'id'——已在文档中说明
8});
9
10pm.test("订单状态语义等价", function () {
11 const statusMap = { "SHIPPED": "shipped", "PENDING": "pending" };
12 pm.expect(v2Response.status).to.equal(
13 statusMap[JSON.parse(v1Response).status]
14 );
15});第五步:API 网关废弃强制执行
API 网关将废弃管理集中到所有上游服务。使用 API7 Enterprise 或 Apache APISIX,可以通过 response-rewrite 插件自动注入废弃头部,无需修改上游服务:
1# 废弃端点的 APISIX 路由配置
2plugins:
3 response-rewrite:
4 headers:
5 set:
6 Deprecation: "Thu, 01 Jan 2026 00:00:00 GMT"
7 Sunset: "Wed, 01 Jul 2026 00:00:00 GMT"
8 Link: '<https://api.example.com/v2/orders>; rel="successor-version"'随时间推移对废弃端点施加更严格的限流,以促进迁移:
1plugins:
2 limit-req:
3 rate: 100 # 废弃端点的降低限流
4 burst: 20
5 rejected_code: 429在 Sunset 日期后通过更新路由配置重定向流量到新版本——无需修改上游服务。
第六步:消费者废弃监控
对于 API 提供方,跟踪哪些消费者仍在使用废弃端点对于规划 Sunset 日期至关重要。对网关或 API 分析进行埋点以报告:
1sequenceDiagram
2 participant 消费者
3 participant 网关 as API 网关
4 participant 上游 as 上游服务
5 participant 分析
6
7 消费者->>网关: GET /v1/orders/1234
8 网关->>分析: 记录:consumer=app-x, endpoint=/v1/orders, deprecated=true
9 网关->>上游: 转发请求
10 上游-->>网关: 200 OK
11 网关-->>消费者: 200 OK + 废弃头部
12
13 Note over 分析: 跟踪消费者迁移进度
14 分析-->>提供方: "app-x 仍在调用 v1——通知团队"废弃生命周期检查清单
废弃 API 版本或端点时,使用此检查清单确保完整的测试覆盖:
| 阶段 | 操作 | 测试验证 |
|---|---|---|
| 公告 | 添加 Deprecation 头部 | 断言头部存在且日期正确 |
| 迁移窗口开放 | 添加 Sunset 头部 + 继任者 Link | 断言两个头部,验证继任者 URL |
| 迁移中期 | 监控消费者用量 | 分析数据显示 v1 流量下降 |
| Sunset 前 | 通知剩余消费者 | 测试 v1 与 v2 的一致性 |
| Sunset 日期 | 移除或禁用端点 | 断言 410 Gone 响应 |
| Sunset 后 | 移除废弃测试套件 | CI 流水线已更新,无 v1 测试残留 |
结论
API 废弃既是治理挑战,也是技术挑战。技术层面——实施标准头部、运行版本感知测试套件、验证迁移一致性——已有充分理解且可自动化。治理层面——沟通时间线、跟踪消费者采用情况、公平执行 Sunset 日期——需要流程纪律。
测试在两个层面都发挥作用。废弃头部的自动断言能在头部意外缺失时捕获回归。一致性测试让人有信心新版本处理了所有现有用例。Sunset 后的测试验证旧端点确实已下线。这些实践共同使 API 演进成为可预测的低风险活动,而非计划外消费者中断的来源。
