Files
2026-07-13 21:36:19 +08:00

40 lines
2.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
# Deepening
如何安全地深化一组浅层模块(给定其依赖关系)。假设您已熟悉 [LANGUAGE.md](LANGUAGE.md) 中的词汇——**module(模块)**、**interface(接口)**、**seam(接缝)**、**adapter(适配器)**。
## 依赖分类
在评估一个可供深化的候选模块时,请对其依赖进行分类。分类决定了深化后的模块如何在其接缝处进行测试。
### 1. 进程内(In-process
纯计算、内存状态、无 I/O。始终可深化——合并模块并通过新接口直接测试。无需适配器。
### 2. 本地可替代(Local-substitutable
具有本地测试替身的依赖(PGLite 替代 Postgres、内存文件系统)。如果替身存在则可深化。深化后的模块使用测试套件中运行的替身进行测试。接缝是内部的;模块外部接口上没有端口。
### 3. 远程但自有(端口与适配器,Ports & Adapters
你自己跨越网络边界的服务(微服务、内部 API)。在接缝处定义一个**端口(port)**(接口)。深层模块拥有逻辑;传输层作为**适配器(adapter)**注入。测试使用内存适配器。生产环境使用 HTTP/gRPC/队列适配器。
建议表述:*"在接缝处定义一个端口,为生产环境实现一个 HTTP 适配器,为测试环境实现一个内存适配器,这样即使跨网络部署,逻辑也位于一个深层模块之中。"*
### 4. 真正的外部(模拟,Mock)
你不控制的第三方服务(Stripe、Twilio 等)。深化后的模块将外部依赖作为注入的端口接收;测试提供模拟适配器。
## 接缝规范
- **一个适配器意味着一个假设性的接缝。两个适配器才意味着一个真实的接缝。** 除非至少有两个适配器有合理理由(通常是生产环境 + 测试环境),否则不要引入端口。单个适配器的接缝只是间接层。
- **内部接缝与外部接缝。** 一个深层模块可以有内部接缝(对其实现私有,供自身测试使用),也可以有其接口处的外部接缝。不要仅仅因为测试使用了内部接缝,就将其暴露到接口上。
## 测试策略:替换,而非叠加
- 一旦深化模块接口处的测试存在,浅层模块上的旧单元测试就变成了废物——删除它们。
- 在深化模块的接口处编写新测试。**接口就是测试面。**
- 测试应通过接口断言可观察到的结果,而非内部状态。
- 测试应能经受内部重构——它们描述的是行为,而非实现。如果实现发生变化时测试也必须随之变化,说明它测试到了接口之外。