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