test(contract): 落成五个接缝的契约骨架,用它逼出七个设计洞
第 ④ 阶段的核心交付物。这套测试不针对任何具体实现,写的是「不管你怎么实现 都必须满足这些行为」,下游写完自己的实现接到 fixture 上跑一遍即可, 是任何新适配器的准入标准(CLAUDE.md §0)。 现在全部跳过,因为公共类型与 Protocol 还没落地。价值不在跑,在写: 写一条契约要求把每次调用逐字写出来——方法叫什么、参数填什么、返回值怎么取, 而散文里读着通顺的地方,落到这一步就露出来了。 三轮文档评审没报出的七个洞,写这套测试时全部撞了出来: 没有动作的步 result_id 填什么;三个 ActionStatus 取值的触发条件; 动作被拒绝时观察的来源(执行器与 SyntheticObservations 两处都有); 解释器能不能抛异常;Event 没有字段所以事件出口的契约只写得出一半; read_log 读不存在的运行必须返回空日志而不是抛异常; 以及原子性与前缀持久性这两条 0005 的承诺根本没有机器兜底—— 写这套测试之前我们默认它们会被契约测试接住。 洞标成 xfail(strict=True) 而不是常驻 fail:一个永远红的套件会训练所有人忽略红。 它们都不带 fixture,否则会被「实现还没有」那个跳过挡住, 于是「答不上来」就伪装成了「还没轮到」。补上之后 XPASS 会报错,逼人回来删标记。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""契约套件的装配点。
|
||||
|
||||
这套测试**不针对任何具体实现**。它写的是「不管你怎么实现,都必须满足这些行为」,所以它
|
||||
自己不造实现,只声明「你得提供什么」。任何一个下游写完自己的存储或适配器,把它接到这里
|
||||
的 fixture 上跑一遍,全绿就算合格——这是 `CLAUDE.md` §0 说的「任何新适配器的准入标准」。
|
||||
|
||||
**接法**:下游在自己的 `conftest.py` 里覆盖同名 fixture,返回自己的实现。
|
||||
|
||||
**现在这套测试全部跳过**,因为 `polyloop` 下还没有任何公共类型与 Protocol
|
||||
(`research-wiki/design/0006-public-names-and-signatures.md` 还没过人类门)。跳过的理由写在
|
||||
每个 fixture 里,读跳过原因就能知道缺的是哪一块。
|
||||
|
||||
**为什么在实现之前就写它**:写一条契约测试要求把每一次调用逐字写出来——方法叫什么、参数
|
||||
填什么、返回值怎么取。散文里读着通顺的地方,落到这一步就会露出来。前三轮文档评审抓不到的
|
||||
洞,几乎全是这么冒出来的。
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
#: 公共类型与 Protocol 落地之前,套件里的每一条都缺同一样东西。
|
||||
_NOT_YET = (
|
||||
"polyloop 的公共类型与 Protocol 还没落地(design/0006 待确认)。"
|
||||
"这条契约要断言的行为已经写在测试的 docstring 里,落地之后去掉这个跳过即可。"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store():
|
||||
"""被测的存储接缝实现。
|
||||
|
||||
下游覆盖这个 fixture,返回自己的实例。每次调用应当返回一个**空的**存储——套件里的每条
|
||||
测试都假设自己面对一份干净的日志,共用状态会让测试之间的顺序变成隐式依赖。
|
||||
"""
|
||||
pytest.skip(_NOT_YET)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def records():
|
||||
"""构造各类记录的辅助工厂。
|
||||
|
||||
它不是被测对象,是让测试正文读得懂的一层薄封装:`records.model_call_intent(...)` 比
|
||||
直接写一长串构造参数更能看出这条测试在断言什么。工厂本身由库提供,因为记录类的字段
|
||||
是库的公共承诺,下游不该为了跑契约测试去手写构造。
|
||||
"""
|
||||
pytest.skip(_NOT_YET)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def action_executor():
|
||||
"""被测的动作执行接缝实现。"""
|
||||
pytest.skip(_NOT_YET)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def decision_parser():
|
||||
"""被测的决策解释接缝实现。"""
|
||||
pytest.skip(_NOT_YET)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def model_client():
|
||||
"""被测的模型调用接缝实现。
|
||||
|
||||
注意这一层的契约测试**不打真实网关**——那是 e2e 的事。这里断言的是返回结构体的形状
|
||||
与失败时的表达方式,用一个受控的替身就能验。
|
||||
"""
|
||||
pytest.skip(_NOT_YET)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event_sink():
|
||||
"""被测的事件出口实现。"""
|
||||
pytest.skip(_NOT_YET)
|
||||
Reference in New Issue
Block a user