# 交付给客户的集成代码，用不了那么多 mock

> Vladimir Khorikov 用一条简洁规则划定 mock 边界，并指出每套测试都绕不开的取舍。

原文: https://fdetimes.net/zh/books-courses/unit-testing-khorikov-when-to-mock/

只 mock 其他系统共享的外部依赖。《Unit Testing: Principles, Practices, and Patterns》的作者 Vladimir Khorikov 在 2020 年于自己的博客 Enterprise Craftsmanship 上提出了这条规则。如果你是一名前线部署工程师（FDE），刚写完一个把客户系统接入公司产品的服务，这句话可能会改变你即将交付的整套测试。

原因在于交接的方式。你离开客户现场后，修改这段集成代码的就是客户的工程团队。只有测试套件能告诉他们刚刚改坏了什么。一套满是 mock 的测试，每次重构都会变红，哪怕根本没有 bug，很快就会被关掉。到那时，这些测试和不存在没有区别。

## 要不要 mock，取决于谁在共享这个依赖

Khorikov 把进程外依赖分成两类。受管依赖只能通过你的应用访问，比如服务自己的数据库。非受管依赖则是其他应用也能访问的依赖。由此他得出规则：受管依赖用真实实例，只 mock 非受管依赖。

设想你为一家分销企业开发了一个服务。它接收订单，存入自己的 Postgres 数据库，再推送到一个 ERP 系统的 API，客户的财务和仓库团队都会读取这个 ERP。

Postgres 是受管依赖，因为你的服务之外没有任何东西会碰它。ERP 是非受管依赖，因为你发给它的调用构成了一份契约，其他系统也依赖这份契约。

套用到这个例子：测试连接真实的 Postgres，在边界处 mock ERP，检查发出的请求是否正确。再假设几个月后，客户团队在一次迁移中重命名了某一列。

如果当初 mock 了数据库，测试依然是绿的，生产环境却已经出错。如果测试跑在真实的 Postgres 上，CI 会立刻变红。而 mock ERP 依然合理：测试只需证明服务始终遵守它与外部世界的契约。

**要点:** 值得信赖的测试套件只在出现真正的 bug 时变红，而不是每次重构都变红。

## 没有哪套测试能三者兼得

下一个问题是如何判断一个测试的好坏。据一份总结本书的笔记，Khorikov 提出了四个支柱：防止回归、抗重构、快速反馈和可维护性。关键在于前三项互相排斥。三者只能选其二，这种取舍和 CAP 定理类似。

回到订单服务。调用真实 ERP 沙箱的端到端测试能发现很多 bug，内部代码重组时也不会失败，但速度很慢。把一切都 mock 掉的小测试跑得飞快，却与实现细节绑在一起，一拆分函数就失败。客户团队最讨厌的正是后一种。

理解了这个取舍，你就不会再去找唯一“正确”的测试类型。对订单服务来说，合理的分配是：大量快速测试覆盖纯业务逻辑，再加上少量跑在真实数据库上的集成测试。

在本书目录中，Manning 列出了用集成测试检验整个系统，以及避免反模式、让测试与生产代码一同重构等内容。

## 适合谁读，按什么顺序读

Manning 于 2020 年 1 月出版本书，全书 304 页。Khorikov 在个人网站上称它是一本完整的单元测试指南，并主张编程规则应当从第一性原理推导出来。

Manning 明确表示，本书面向已经掌握单元测试基础的读者。如果你从没写过测试，先学一个测试框架再回来读。示例用 C# 编写，但出版社表示同样适用于任何语言。

如果你的时间被客户日程切得零零碎碎，可以试试这个顺序。先读博客文章“When to Mock”，一个晚上就够了。

接着读书中讲四个支柱的部分，因为你要用这些标准给每个测试打分。最后读 mock 和集成测试的章节，同时打开你正在写的代码对照着看。

Manning 承诺帮助读者用现代实践改进已有的测试套件，这正好契合 FDE 的实际情况。你很少有机会从零开始写测试，更多时候是接手一个客户代码库，里面已经有一堆又慢又脆弱、到处是 mock 的测试。

## 把这本书变成面试优势

大多数候选人在简历上只写一句“编写了单元测试”。你可以写得更具体：集成测试跑在真实数据库上，mock 只放在与其他系统共享的 API 边界，交接后客户团队重构时测试套件依然稳定。

这一句能让招聘经理看到，你懂的不只是语法，还有交接。

面试中如果被问到集成系统的测试策略，用取舍来回答，而不是报覆盖率。说清楚每一层测试在三个支柱中选了哪两个，以及原因。这正是 Khorikov 倡导的第一性原理思维。

你离开现场后，代码会留下来。客户团队还没想到要问的问题，测试套件会替你回答。

**本周动手试试:**

- 列出你正在开发的集成服务中所有进程外依赖，并逐一标注是受管还是非受管
- 挑一个 mock 了应用自身数据库的测试，改写成在容器中连接真实数据库运行
- 决定是否买书之前，先读 enterprisecraftsmanship.com 上的“When to Mock”一文

## 信源

- [Unit Testing Principles, Practices, and Patterns (Manning)](https://www.manning.com/books/unit-testing)

- [Enterprise Craftsmanship: Unit Testing book page](https://enterprisecraftsmanship.com/book/)

- [When to Mock](https://enterprisecraftsmanship.com/posts/when-to-mock/)

- [Four Pillars of Good Unit Tests](https://notesbylex.com/four-pillars-of-good-unit-tests)
