测试、静态检查与 CI
自动化代码的风险不取决于代码量,而取决于权限与影响范围。一个删除镜像、重启服务或批量修改集群的十行脚本,必须像应用代码一样经过测试与发布门禁。
1. 测试分层
| 层级 | 验证对象 | 是否访问真实生产系统 |
|---|---|---|
| 单元测试 | 规则、配置校验、请求构造、重试和退出码 | 否 |
| 集成测试 | 对测试 Redis、测试 API 或 kind 集群的真实协议行为 | 仅隔离测试环境 |
| 契约测试 | 外部 API 字段、状态码和分页语义 | 测试租户或 mock server |
| 演练 | 计划、审批、执行、审计、回滚和告警链路 | 预生产 |
生产凭据、生产 kubeconfig 和真实变更接口不应出现在 CI 中。读取生产状态的 smoke test 也必须使用独立的只读身份。
2. 为 HTTP 客户端写单元测试
把网络调用注入业务函数,测试就无需联网。下面使用 httpx.MockTransport 模拟“第一次 503、第二次成功”。
# tests/test_retry.py
import httpx
from ops_tool.http import post_with_retry
def test_post_retries_with_same_idempotency_key() -> None:
calls: list[httpx.Request] = []
def handler(request: httpx.Request) -> httpx.Response:
calls.append(request)
# 第一次模拟暂时性故障,第二次返回成功。
if len(calls) == 1:
return httpx.Response(503, request=request)
return httpx.Response(202, json={"job_id": "j-1"}, request=request)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport) as client:
response = post_with_retry(
client,
"https://ops.test/v1/restarts",
{"host": "web-01"},
idempotency_key="change-1",
attempts=2,
)
assert response.status_code == 202
assert len(calls) == 2
# 重试必须沿用同一 key,服务端才能识别重复请求。
assert {r.headers["Idempotency-Key"] for r in calls} == {"change-1"}
对 SSH、Docker 和 Kubernetes 客户端使用 adapter interface 或 fixture 替身。测试应验证“调用了什么、没有调用什么”,特别是 dry-run 时绝不能调用变更方法。
3. 覆盖危险分支
每一个变更模块至少覆盖以下断言:
- 不在目标白名单、环境不匹配或缺少审批时拒绝执行;
dry-run输出完整计划,但不会产生网络或 SSH 写调用;- 相同
change_id重试时不会再次执行; - 部分失败会给出逐目标结果,且恢复后可以安全重跑;
- 密钥、Authorization Header 和完整 payload 不会出现在日志或异常文本中。
4. 静态检查
# 格式与常见错误:未使用变量、错误导入、危险的裸 except 等。
uv run ruff format --check .
uv run ruff check .
# 类型检查能发现 Optional、字典字段与客户端返回值的许多边界问题。
uv run mypy src
# 快速执行单元测试;CI 中不要因没有测试而静默通过。
uv run pytest -q
静态检查不能证明变更安全,但能在调用外部系统前捕获大量低成本错误。为关键模块启用严格类型检查,逐步收紧而不是用全局忽略掩盖错误。
5. GitLab CI 最小门禁
# .gitlab-ci.yml
image: ghcr.io/astral-sh/uv:python3.12-bookworm
stages: [quality, test]
before_script:
# 严格按锁文件安装,CI 与发布环境获得相同依赖集合。
- uv sync --locked --all-groups
quality:
stage: quality
script:
- uv run ruff format --check .
- uv run ruff check .
- uv run mypy src
unit-test:
stage: test
script:
# 单元测试只使用 mock,不接触生产 Token 和生产网络。
- uv run pytest -q
部署任务应与质量任务分离:只在受保护分支、受保护环境和人工批准后执行;部署使用短期凭据,不复用测试阶段的 Token。
6. 发布证据
合并请求至少应保留:测试结果、变更计划、依赖差异、审阅人和关联工单。高风险自动化还应保存执行前后的资源版本、操作者、任务 ID 和回滚记录。CI 成功是必要条件,不是变更授权本身。