Deepening
如何在给定 dependency 下安全 deepen 一簇 shallow module。假设 LANGUAGE.md 词汇——module、interface、seam、adapter。
Dependency 类别
评估 deepening 候选时,分类其 dependency。类别决定 deepened module 如何跨 seam 测试。
1. In-process
纯计算、内存 state、无 I/O。始终可 deepen——合并 module 并直接通过新 interface 测试。无需 adapter。
2. Local-substitutable
有 local test stand-in 的 dependency(Postgres 用 PGLite、in-memory filesystem)。若 stand-in 存在则可 deepen。deepened module 在 test suite 中用 stand-in 测试。seam 在内部;module 外部 interface 无 port。
3. Remote but owned(Ports & Adapters)
跨网络边界的自有 service(microservice、internal API)。在 seam 定义 port(interface)。deep module 拥有 logic;transport 作为 adapter 注入。测试用 in-memory adapter。生产用 HTTP/gRPC/queue adapter。
建议形状:"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."
4. True external(Mock)
不可控的第三方 service(Stripe、Twilio 等)。deepened module 将 external dependency 作为 injected port;测试提供 mock adapter。
Seam discipline
- One adapter means a hypothetical seam. Two adapters means a real one. 除非至少两个 adapter justified(通常 production + test),否则不要引入 port。单 adapter seam 只是 indirection。
- Internal seams vs external seams。 deep module 可有 internal seam(implementation 私有,自有 test 使用),以及 interface 处的 external seam。不要因为 test 使用就把 internal seam 暴露到 interface。
测试策略:replace,不要 layer
- shallow module 上的旧 unit test,在 deepened module interface 有 test 后成为 waste——删除。
- 在 deepened module interface 写新 test。The interface is the test surface.
- test 断言 interface 的可观察 outcome,非 internal state。
- test 应 survive internal refactor——描述 behavior,非 implementation。若 implementation 变 test 就得变,说明 test 过了 interface。