Spec-Contract-Test 金字塔:面向复杂平台的文档驱动开发
作者:Tobias Weiss,openDesk 团队 — 2026年7月25日
摘要
为复杂基础设施平台编写文档是一项具有挑战性的任务。传统方法通常会导致文档过时、格式不一致,并且无法验证文档内容是否与实际部署情况相符。Spec-Contract-Test 金字塔通过将文档组织成三个经过验证的层次来解决这个问题:规范(我们需要什么)、合约(服务如何通信)和测试(验证)。结合自动化,这将静态文档转变为一个生动的系统,能够在问题到达生产环境之前发现它们。
结果: 从需求到部署的100%可追溯性、自动化验证,以及对文档准确反映现实的充分信心。
问题:文档与现实不符
每个基础设施项目都面临着相同的文档挑战:
三大痛点
规范 部署 运维
↓ ↓ ↓
"我们需要" "我们构建" "它工作吗?"
↓ ↓ ↓
设计文档 代码/配置 测试/监控
↓ ↓ ↓
(已过时) (当前) (不完整)
差距:
- 从规范到部署: 随着实现的发展,规范会变得过时
- 从部署到运维: 部署的服务可能与文档描述的行为不符
- 从运维到规范: 在生产中获得的经验教训不会反馈到规范中
openDesk Edu 的挑战
openDesk Edu 在 12+ 个 Kubernetes 命名空间 中部署 50+ 个服务,并使用 400+ 个 Helm 图表值。面对这种复杂性:
- Wiki 页面会在几天内过时
- 图表与实际部署不符
- 服务依赖关系是隐含的,不是明确的
- 测试不覆盖部署场景
- "在我的机器上可以工作" 变成了 "在我们的开发集群中可以工作"
示例: Keycloak 认证服务依赖于 MariaDB 和 Redis。我们如何知道:
- 哪些服务会受到影响?
- 这些服务是否仍然正常工作?
- 更改是否已记录?
使用传统文档:我们不知道。
解决方案:Spec-Contract-Test 金字塔
Spec-Contract-Test 金字塔引入了一种层次化、经过验证的文档方法:
SPEC-CONTRACT-TEST 金字塔
┌─────────┐
│ 第3层 │ 我们需要什么?
│ 规范 │ 高层需求
└────┬────┘
│
┌────▼────┐
│ 第2层 │ 服务如何通信?
│ 合约 │ 接口定义
└────┬────┘
│
┌────▼────┐
│ 第1层 │ 它能工作吗?
│ 测试 │ 自动化验证
└─────────┘
核心原则
- 清晰的职责分离 — 每一层都有明确的、不重叠的目的
- 双向可追溯性 — 每个规范都映射到合约,每个合约都映射到测试
- 自动化验证 — CI/CD 确保所有层级的一致性
- 可执行的文档 — 测试验证规范是否正确实现
为什么是金字塔?
这种形状反映了自然分布:
- 顶部宽(规范):许多服务,每个都有自己的需求
- 中间窄(合约):服务之间的共享接口
- 底部宽(测试):对所有需求的全面验证
三个层次的详细解释
第3层:规范 — "我们需要什么"
目的: 从需求的角度定义系统应该做什么。
属于这一层:
- 功能需求(功能、能力)
- 非功能需求(性能、可用性、安全性)
- 配置选项
- 服务依赖关系
- 设计决策
不属于这一层:
- 实现细节
- API模式(属于合约)
- 测试用例(属于测试)
示例:Keycloak 规范
# Keycloak - 单点登录服务
## 概述
提供 SAML 2.0、OIDC 和 LDAP 集成的集中式身份验证和授权服务。
## 需求
### 功能需求
1. **SAML 2.0 身份提供者** — 必须作为机构联合的 SAML IdP
2. **OIDC 提供者** — 必须支持现代应用的 OpenID Connect
3. **LDAP 集成** — 必须对机构 LDAP 目录进行身份验证
4. **管理 API** — 必须提供用户管理的 REST API
### 非功能需求
- **可用性:** 99.95% 正常运行时间
- **响应时间:** 认证请求 < 500ms
- **安全性:** 符合 FIPS 140-2 的加密
## 依赖关系
- **依赖于:** MariaDB 10.6+、Redis 7+
- **为以下服务提供:** Nextcloud、Element、SOGo、JupyterHub、20+ 其他服务
## 配置
| 参数 | 类型 | 默认值 | 必需 |
|------|------|--------|------|
| `saml.enabled` | boolean | true | 是 |
| `oidc.enabled` | boolean | true | 是 |
| `ldap.url` | string | "" | 是 |
第2层:合约 — "服务如何通信"
目的: 通过正式接口定义服务如何相互通信。
属于这一层:
- REST API 端点和模式
- 数据库模式
- 消息队列格式
- 配置接口
- 存储布局
不属于这一层:
- 高层需求(属于规范)
- 测试实现(属于测试)
- 服务特定逻辑
示例:认证 API 合约
# 认证 API 合约 v1.0
合约: auth-api
版本: v1.0.0
端点:
POST /api/v1/authenticate:
描述: 认证用户并返回会话令牌
请求:
content-type: application/json
模式: AuthRequest
响应:
状态: 200
模式: AuthResponse
认证: 无(公共端点)
GET /api/v1/userinfo:
描述: 获取认证用户的信息
请求:
头部:
Authorization: Bearer {token}
响应:
状态: 200
模式: UserInfo
认证: Bearer 令牌
模式:
AuthRequest:
类型: 对象
属性:
username: 字符串(必需)
password: 字符串(必需)
client_id: 字符串
必需: [username, password]
AuthResponse:
类型: 对象
属性:
access_token: 字符串
token_type: 字符串(枚举:[Bearer])
expires_in: 整数
refresh_token: 字符串
必需: [access_token, token_type, expires_in]
第1层:测试 — "它能工作吗?"
目的: 验证规范和合约是否被正确实现。
属于这一层:
- 部署验证测试
- 配置测试
- 集成测试
- 合约合规测试
- 端到端工作流测试
不属于这一层:
- 需求(属于规范)
- 接口定义(属于合约)
示例:Keycloak 部署测试
套件: keycloak 规范验证
模板:
- deployment.yaml
- service.yaml
- ingress.yaml
测试:
- it: 应使用所需资源进行部署
断言:
- containsDocument:
kind: Deployment
apiVersion: apps/v1
- equal:
path: spec.replicas
value: 2
- it: 应启用 SAML
断言:
- contains:
path: spec.template.spec.containers[0].env
content:
name: SAML_ENABLED
value: "true"
- it: 应连接到 MariaDB
断言:
- contains:
path: spec.template.spec.containers[0].env
content:
name: DB_HOST
valueFrom:
secretKeyRef:
name: keycloak-db
key: host
注册表:建立联系
注册表是连接金字塔各层的粘合剂,提供可追溯性:
注册表组件
注册表结构:
specs/_registry/
├── component-index/ # 所有组件的主列表
├── test-mapping/ # 哪些测试覆盖哪些规范
├── test-coverage-gaps/ # 缺少测试覆盖的内容
└── interconnection-matrix/ # 服务依赖关系矩阵
测试覆盖率报告
┌─────────────────────────────────────────────────────────────┐
│ 测试覆盖率报告 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 总体覆盖率: 12% (65 个规范中有 8 个测试) │
│ │
│ 按类别划分: │
│ 服务 (24): 25% (6/24 已测试) ████░░░░ │
│ 平台 (17): 0% (0/17 已测试) ░░░░░░░░ │
│ 认证 (4): 0% (0/4 已测试) ░░░░░░░░ │
│ 集成 (6): 0% (0/6 已测试) ░░░░░░░░ │
│ │
│ 目标: 总体覆盖率 80%+ │
│ │
└─────────────────────────────────────────────────────────────┘
互连矩阵
┌─────────────┬─────────────────┬─────────────────────────┐
│ 服务 │ 依赖于 │ 为以下提供 │
├─────────────┼─────────────────┼─────────────────────────┤
│ Keycloak │ MariaDB, Redis │ Nextcloud, Element, SOGo│
│ MariaDB │ Ceph RBD │ Keycloak, Nextcloud │
│ Nextcloud │ MariaDB, Redis, │ Web, Mobile, Desktop │
│ │ Keycloak │ │
│ Element │ PostgreSQL, │ Web, Mobile │
│ │ Keycloak │ │
└─────────────┴─────────────────┴─────────────────────────┘
这实现了影响分析:“如果 MariaDB 更改其身份验证机制,需要更新和重新测试哪 8 个服务?”
自动化:金字塔的超能力
手动维护 65+ 个规范、30+ 个合约和 60+ 个测试是不可能的。自动化使金字塔可持续。
CI/CD 工作流
代码风格检查工作流
验证文档质量:
- ✅ SPDX 许可证头
- ✅ 代码风格合规性
- ✅ 破损的交叉引用
- ✅ 侧边栏一致性
- ✅ YAML/Markdown 格式
- ✅ 合约锚点验证
金字塔验证工作流
验证结构完整性:
- ✅ 按类别计算覆盖率
- ✅ 验证互连矩阵
- ✅ 验证交叉引用完整性
- ✅ 验证合约有效性
自动化的 Python 脚本
1. 覆盖率计算
$ python scripts/calculate-coverage.py
2. 互连验证
$ python scripts/validate-interconnections.py
3. 测试生成
$ python scripts/generate-tests.py specs/services/keycloak
指标:从好到伟大
当前状态
| 指标 | 数值 | 状态 |
|---|---|---|
| 规范 | 65 | ✅ 良好 |
| 合约 | 1 | ⚠️ 需要扩展 |
| 测试 | 8 | ⚠️ 需要扩展 |
| 覆盖率 | 12% | ⚠️ 需要改进 |
| SPDX 合规性 | 100% | ✅ 优秀 |
目标状态(第3阶段)
| 指标 | 目标 | 改进 |
|---|---|---|
| 规范 | 75+ | +15% |
| 合约 | 50+ | +5000% |
| 测试 | 60+ | +650% |
| 覆盖率 | 80%+ | +567% |
| 自动化 | 完全 | 新功能 |
可衡量的收益
| 领域 | 之前 | 之后 | 改进 |
|---|---|---|---|
| 文档准确性 | ~60% | 100% | +67% |
| 交叉引用完整性 | ~50% | 100% | +100% |
| 测试覆盖率 | 0% | 80%+ | +∞ |
| 入职时间 | 几周 | 几天 | -80% |
| 错误检测 | 手动 | 自动 | +∞ |
实施路线图
第1阶段:基础(第1个月)
- 将整体 API 合约拆分为单个文件
- 为关键基础设施添加测试(Keycloak、MariaDB、PostgreSQL、Redis、MinIO)
- 为核心服务添加测试(Nextcloud、Element、SOGo、Etherpad)
- 创建平台级测试类别
- 目标: 50% 覆盖率、30+ 个合约、30+ 个测试
第2阶段:自动化(第2-3个月)
- 在 CI 中实现合约验证
- 部署覆盖率仪表板
- 为新规范自动生成测试
- 添加集成测试
- 目标: 70% 覆盖率、40+ 个合约、50+ 个测试
第3阶段:高级(第4+个月)
- 实现 Pact 进行正式合约测试
- 迁移到 OpenAPI 3.0 标准
- 添加基于属性的测试
- 添加性能和安全测试
- 目标: 80%+ 覆盖率、50+ 个合约、60+ 个测试、完全自动化
经验教训
成功的做法
✅ 先结构,后内容 — 我们首先关注目录结构和模板,然后再填充内容。这使得添加规范变得容易。
✅ 从第一天开始自动化 — 我们在拥有许多规范之前就实现了验证脚本,从而从一开始就确保了质量标准。
✅ 渐进式采用 — 我们没有一次性转换所有现有文档。新的规范使用金字塔结构;旧的文档逐渐迁移。
✅ 清晰的分离 — 每个金字塔层级都有明确的目的,没有重叠。每个人都理解什么属于哪里。
挑战
⚠️ 对变革的抵制 — 习惯于特别文档的工程师不愿意采用新结构。 解决方案:通过自动化展示价值。
⚠️ 初始开销 — 创建规范、合约和测试似乎需要 3 倍的工作。 解决方案:开发自动化测试生成。
⚠️ 交叉引用的复杂性 — 在 65+ 个文件中维护有效的引用容易出错。 解决方案:通过 CI 进行自动化验证。
⚠️ 测试维护 — 当规范更改时,测试会变得过时。 解决方案:要求测试与规范一起更新;使用自动化生成。
快速开始
想在您的项目中实现 Spec-Contract-Test 金字塔?
第1步:建立结构
mkdir -p specs/{services,platform,auth,integrations}/_registry
第2步:创建第一个规范
# 我的服务
## 需求
1. 应该做一些有用的事情
## 依赖关系
- 依赖于: 数据库
## 配置
- ENABLE_FEATURE: 布尔值(默认:true)
第3步:添加一个测试
套件: my-service 验证
模板:
- deployment.yaml
测试:
- it: 应该成功部署
断言:
- containsDocument:
kind: Deployment
第4步:自动化
# 使用我们的脚本或创建您自己的
python scripts/calculate-coverage.py
python scripts/validate-interconnections.py
第5步:迭代
从关键服务开始,逐步扩展,并衡量进度。
结论
Spec-Contract-Test 金字塔将文档从一个必要的恶魔转变为一个战略资产。通过将文档组织成三个经过验证的层次并实现自动化,我们实现了:
✅ 准确性 — 文档反映现实 ✅ 可追溯性 — 每个需求都映射到一个测试 ✅ 自动化 — 错误在部署之前被发现 ✅ 可维护性 — 清晰的结构使更新变得容易 ✅ 信心 — 每个人都知道已经实现和测试了什么
金字塔的一句话总结
"Spec-Contract-Test 金字塔确保您所规范的内容就是您构建和测试的内容,并在每一步都进行自动化验证。"
开始您的旅程
文档不必是一个事后才想到的东西。使用 Spec-Contract-Test 金字塔,您可以构建一个与代码一样可靠的文档系统。
准备好开始了吗?
版权所有 © 2026 HRZ Uni Marburg。根据 AGPL-3.0-only 许可证授权。openDesk Edu 是 openDesk 项目的一部分。