合同測試
1)合同在哪裏適用
HTTP REST/JSON:資源、分頁、過濾器、等效性、錯誤代碼。
gRPC/Protobuf:消息類型,狀態,「deadline」語義,backward-compat v.proto。
GraphQL:電路,非空格,指令,對字段進行沖洗。
消息/流(Kafka/Pulsar/SQS):事件方案(Avro/JSON/Protobuf),分期密鑰,順序,等效密鑰。
內部SDK/庫:公共功能/例外/性能合同。
2)CDC模型: 角色和工件
消費者發布期望合同(大致查詢/回答,類型匹配器,不變量)。
供應商對其服務/適配器/手柄進行合同驗證。
合同經紀人(Pact Broker/Backstage/文物-repo)存儲版本,標簽(「prod」,「staging」,「canary」)和兼容性矩陣「consumer@v → provider@v」。
發布策略:如果違反任何「prod相關」合同,則禁止派遣提供商。
3)在合同中記錄的內容(HTTP示例)
最低限度:- 方法/路徑/參數/標題(包括auth,等效密鑰)。
- 主體和類型對決器(類型/格式/regexp/範圍)。
- 錯誤代碼和結構;穩定的「error_code」。
- 語義不變性:排序,唯一性,單調性「created_at」。
- 非功能預期(可選):p95、尺寸限制、限額標題。
json
{
"interaction": "GET /v1/users/{id}",
"request": { "method": "GET", "path": "/v1/users/123", "headers": {"Accept":"application/json"} },
"matchers": {
"response.body.id": "type:number",
"response.body.email": "regex:^.+@.+\\..+$",
"response.body.created_at": "format:rfc3339"
},
"response": {
"status": 200,
"headers": {"Content-Type":"application/json"},
"body": {"id": 123, "email": "alice@example.com", "created_at": "2025-10-31T12:00:00Z"}
},
"error_cases": [
{
"name":"not_found",
"request":{"path":"/v1/users/9999"},
"response":{"status":404, "body":{"error_code":"USER_NOT_FOUND"}}
}
]
}
4)事件合同(事件驅動)
事件方案:「類型」,「版本」,「id」,「occurred_at_utc」,「生產者」,「主題」,「付費」。
不變性:「id」的不變性和「(類型,id)」的冪等性,派系鍵內的順序,「序列」的單調性。
Schema Registry:存儲演變和兼容性規則(backward/forward/full)。
Consumer合同測試:回應「黃金」事件和陰性階段(未知字段,無效)。
json
{
"type":"record","name":"UserRegistered","namespace":"events.v1",
"fields":[
{"name":"id","type":"string"},
{"name":"occurred_at_utc","type":{"type":"long","logicalType":"timestamp-millis"}},
{"name":"email","type":"string"},
{"name":"marketing_opt_in","type":["null","boolean"],"default":null}
]
}
5)進化與互操作性
合同版本:語義'MAJOR。MINOR.PATCH'(MAJOR-打破)。
REST的規則:- 不要打破:不要刪除字段,不要更改類型/值'error_code'。
- 添加可選的默認字段;新的endpoint而不是「魔術」。
- 消除:聲明、並行支持、按指標刪除。
- GraphQL:字段僅添加,非空通過相位輸入;撤消指令。
- gRPC/Proto:不要重新使用字段編號;只添加新的選項。
- 活動:「vN」電路;消費者必須忽略未知字段(懶惰性)。
6)否定和不變檢查
Negative:不正確的類型、禁止的值、沖突參數、超過限制。
Invariants:響應排序,「id」的唯一性,「next_cursor」的正確性,重復時等效響應的穩定性。
時間方面的合同:「created_at」 RFC3339/UTC,正確的本地日投影不是運輸合同的一部分-被賦予業務不變性。
7)生成和本地開發
從合同中產生了供貨商來開發消費者。
對於事件-模式下的「有效/邊界」消息生成器。
Stabs標記合同版本和組裝日期;禁止發布到prod。
8)嵌入CI/CD(參考管道)
1.Consumer CI:
Lint/組裝 →合同生成→ unit/合同測試 →在合同經紀人中發布(標簽: 'consumer@1。7.0`).
2.Provider CI:
在當地/容器中提升服務→有關合同(「prod」/「staging」)→驗證→向經紀人發布狀態。
3.Release Gate:
如果合同尚未完成,則拒絕提供商。
4.Nightly Matrix:
兼容性矩陣「消費者版本×提供者版本」;報告和警報。
9)跨域實踐示例
9.1 REST:遊標分區(合同不變量)
答案包含「items[]」,「next_cursor」(不可用),「limit」和「total」(可選)。
不變量:「len(items)≤ limit」,使用相同的「cursor」 →等效集重新調用。
如果同時設置「cursor」和「page」,則出錯。
9.2 POST等效性
合同要求標題「Idempotency-Key」。
不變性:使用相同鍵的重復查詢返回相同的「id」/狀態。
9.3事件:秩序保證
合同中的分期付款密鑰為'partition_key=user_id'。
不變性:「序列」在鍵內單調增加;消費者必須處理重復。
10)合同中的安全和隱私
在示例中不包括PDn/秘密-僅合成。
記錄強制性安全標題:「授權」,「X-Signature」,「Replay-Prevention」。
對於webhooks,是「2xx」/retraes的簽名和響應合同。
在合同測試日誌中-掩蓋敏感字段。
11)工具
Pact/Pactflow/Pact Broker-HTTP/消息合同,兼容性矩陣。
OpenAPI/AsyncAPI-規範+測試生成器(Dredd,Schemathesis)。
空手道/REST保證是REST合同的腳本驗證。
Protobuf/gRPC-「buf」,「protolint」,兼容性測試;流中的Avro/JSON/Proto的計劃註冊。
GraphQL(graphql compat),快照電路測試的匹配測試。
12)提供商驗證偽代碼(簡化)
python def verify_contract(provider, contract):
for case in contract["cases"]:
req = build_request(case["request"])
res = provider.handle(req) # локально/контейнер assert match_status(res.status, case["response"]["status"])
assert match_headers(res.headers, case["response"].get("headers", {}))
assert match_body(res.body, case["matchers"], allow_extra_fields=True)
for neg in contract.get("error_cases", []):
res = provider.handle(build_request(neg["request"]))
assert res.status == neg["response"]["status"]
assert res.json.get("error_code") == neg["response"]["body"]["error_code"]
13)反模式
「Postman截圖是合同」:沒有版本/標準對決/自動驗證。
超針:合同記錄精確值而不是類型/模式→虛假下降。
一個用於不同區域/通道的一般合同:忽略可變性(標誌,地理規則)。
沒有經紀人/矩陣的合同:無法理解哪些版本是兼容的。
以e2e代替合同:緩慢,昂貴,不穩定。
沒有負數/不變量:僅測試「綠色軌道」。
14)可觀察性和操作
將狀態導出到broker+dashboard「健康合同」。
Alerts:提供商對「prod」合同的新跌幅,事件中「未知領域」的增長。
跟蹤:「contract_id」、「version」、「decision_id」在驗證日誌中。
15)撤消程序
1.添加字段/端口(不中斷)。
2.在規範中將舊標記為「deprecated」,宣布時間表。
3.通過記錄/經紀人跟蹤消費者;遷移海德。
4.在stadge中啟用「shady」 deny (dry-run),然後啟用enforce。
5.在零使用比例後刪除並確認兼容性。
16)建築師支票清單
1.確定了消費者及其所有者?合同是否被驗證?
2.是否存在與環境標簽兼容的經紀人和矩陣?
3.合同中是否包括負值和不變量(特質,遊標,排序)?
4.是否為事件配置了Schema Registry和兼容性模式?
5.在違反軟件合同時,pipline是否會阻止提供商的發布?
6.描述了降解過程和進化策略?
7.從合同中生成穩定器,有本地事件發生器嗎?
8.是否記錄了PD掩蔽和強制性安全標題?
9.合同指標/Alerta是否已連接,是否有漂移報告?
10.雙方(消費者和提供者)的CI審查合同?
二.結論
合同測試將有關相互作用的「真相」帶入可轉換的工件,並使集成可預測。CDC、合同經紀人和計劃演變學科正在用一個可管理的過程取代「突破驚喜」:快速驗證、清晰的不變性和透明版本兼容性。這降低了e2e的成本,加快了發行速度,並提高了整個平臺的質量。