40 lines
2.6 KiB
Markdown
40 lines
2.6 KiB
Markdown
---
|
||
|
||
# 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 等)。深化后的模块将外部依赖作为注入的端口接收;测试提供模拟适配器。
|
||
|
||
## 接缝规范
|
||
|
||
- **一个适配器意味着一个假设性的接缝。两个适配器才意味着一个真实的接缝。** 除非至少有两个适配器有合理理由(通常是生产环境 + 测试环境),否则不要引入端口。单个适配器的接缝只是间接层。
|
||
- **内部接缝与外部接缝。** 一个深层模块可以有内部接缝(对其实现私有,供自身测试使用),也可以有其接口处的外部接缝。不要仅仅因为测试使用了内部接缝,就将其暴露到接口上。
|
||
|
||
## 测试策略:替换,而非叠加
|
||
|
||
- 一旦深化模块接口处的测试存在,浅层模块上的旧单元测试就变成了废物——删除它们。
|
||
- 在深化模块的接口处编写新测试。**接口就是测试面。**
|
||
- 测试应通过接口断言可观察到的结果,而非内部状态。
|
||
- 测试应能经受内部重构——它们描述的是行为,而非实现。如果实现发生变化时测试也必须随之变化,说明它测试到了接口之外。
|