# 客户现场跑通一次，只是一个没人能重跑的测试

> 谷歌SRE一书说没测过就当它坏了；真正的交接要留下测试、可度量的验收标准和运行手册，让客户团队敢改代码。

原文: https://fdetimes.net/zh/analysis/client-site-run-not-handover-proof/

Google 的《Site Reliability Engineering》一书中，讲可靠性测试的那一章开头有一句简短的题记：没试过的东西，就当它是坏的。几段之后，书里说得更进一步：测试通过不一定能证明系统可靠，但测试失败通常能证明系统不可靠。

在给客户发消息说“搞定了，现场已经跑起来了”之前，先想想这两句话。仔细看，在客户线上系统里顺利跑一次，不过是通过了一个测试。这个测试没人写下来，也没人能再跑一遍。

这一点之所以重要，是因为每个前线部署工程师（FDE）项目最后都要交接。Educative 讲 AI 系统 FDE 的课程里说，客户团队无法运维自己不理解的东西，也无法维护只为 FDE 设计的东西。从交接那天起，问题就从“能不能用？”变成了“改起来安不安全？”

## 顺利跑一次能证明什么？

看一个典型场景。你给客户写了一个脚本：从旧系统读取订单导出文件，统一日期和货币单位的格式，再把数据导入新的数据仓库。脚本在最近三天的数据上运行顺利，客户看了看仪表盘，点了点头。

这次运行证明了三件事：这三天的文件格式符合你的假设，访问权限配置正确，数据管道的每一步都没有失败。

至于某个分店导出的日期换了格式、某一行缺了客户 ID，或者下个月客户的工程师改了某个列名，会发生什么，这次运行什么也说明不了。这些情况都还没发生过，顺利运行自然无法提供任何信息。

公平地说，在真实数据上运行必不可少。PostHog 介绍 FDE 岗位的文章写道，FDE 会一直和客户一起推进，直到客户在真实数据上看到真实结果。但这是验收的条件，不是系统经得起变更的证据。

SRE 书点明了其中的差别：每个在变更前后都能通过的测试，都会减少分析人员需要考虑的不确定性。关键在于“前后”二字。现场的那次顺利运行没有“后”，因为代码改动时没人会再跑一遍。

## 为什么 FDE 的代码常常没有测试？

主要原因在于工作的组织方式。只为一个客户写代码，又要赶在演示前出结果，测试最容易被砍掉。

这套理由听上去很合理：代码只在一个地方用，出了问题你就在旁边，随时能修。可这套理由只在你离开之前成立。

Second File 在 Substack 上一篇讲 Field-to-Product 模式的文章里提醒了两件事：只存在于某个客户账户里的定制代码，以及成为单点故障的“英雄”工程师。没有测试的代码恰恰会把你变成这种英雄，因为只有你知道它该做什么、不该做什么。

## 接手代码的人来买单

《Software Engineering at Google》写道，测试软件还有一个同样重要的理由：保住修改软件的能力。对即将交接的项目来说，后果很直接。没有测试的代码仍然可以改，但对客户团队来说，每次修改都是一场赌博。

而且代价会随时间增长。同一本书说，缺陷在开发生命周期中发现得越晚，修复成本越高，很多情况下呈指数级增长。对 FDE 的代码而言，最晚的时刻就是作者已经转去别的项目，缺陷才在客户的生产环境中暴露出来。

测试还能做到书面文档很少做好的一件事。一个只检查单一行为的简短测试，能告诉客户团队你原本打算让脚本如何处理没有客户 ID 的行。它比任何 wiki 页面都可靠，因为它会运行。

**要点:** 交接时要问的不是代码能不能跑，而是别人能不能安全地修改它。

## 交接证据是什么样的？

如果顺利跑一次还不够，那什么才够？下表把顺利运行放在第一行作为基准，再列出另外五类证据。每类证据回答的问题不同，彼此不能替代。

| 证据 | 能证明 | 不能证明 | 谁最需要 |
|---|---|---|---|
| 在客户现场顺利运行一次 | 代码在当天的数据和配置下没有出错 | 代码能应对新数据、新配置或新改动 | 演示时的客户 |
| 项目开始时约定的、用数字定义的“可用”标准 | 双方对成功的含义达成一致 | 代码是否易于维护 | 客户方的项目负责人 |
| 自动化测试，每个测试只验证一个行为 | 预期行为在每次修改后依然成立 | 代码满足客户的业务目标 | 修改代码时的客户工程师 |
| 根据现场缺陷编写的回归测试 | 已知缺陷不会再出现 | 还没人遇到过的缺陷 | 在各个缺陷所在位置修改代码的人 |
| 定时运行的生产环境测试 | 线上系统长期保持健康 | 某次具体改动是否破坏了某个行为 | 运维团队 |
| 附带复现用例的上游 PR | 修复在核心产品中有了归宿 | 客户剩余的定制代码是否可靠 | 产品团队 |

Truong Phung 在 dev.to 上发表的一份 FDE 实践手册用一个对比概括了这种差别：顾问留下的是文档，FDE 留下的是一个运行中的系统，以及组织运维这个系统的能力。表中第一行之下的五类证据，就是这种能力，而且是客户团队可以自行检验的形式。

这份手册还建议，从一开始就把要留下的交付物写进项目定义里，包括评测、运维手册和对客户方负责人的培训，而不是拖到最后。作者提醒，没有这些，系统会在你离开后 90 天内逐渐腐化。

## 把现场的每个缺陷都变成测试

给老代码补齐完整的测试套件并非必需。最朴素的起点，是把现场发现的每个缺陷都记录成一个测试用例。对 FDE 来说，这几乎不增加成本，因为修缺陷时，引发缺陷的输入就在手边。

回到那个订单脚本。假设第二周某个分店发来的文件里，日期按日-月-年书写，而脚本把它读成了月-日-年。

你修好了它，再多花十分钟写一个测试，用的就是那三行数据，检查日期是否被正确解析。从此以后，任何客户工程师改动日期解析函数时，只要再次弄坏这个场景，测试马上就会失败。

下一步是把值得保留的东西移进产品。Field-to-Product 那篇文章用一句话概括了泛化这一步：把抽象和测试推到上游，淘汰本地补丁。

上面的日期解析缺陷就是很好的候选。如果产品本身有导入功能，这个测试就应该放进产品仓库，而不只是留在某个客户的定制目录里。

决定成败的是时机。这些工作大多必须在项目第一周就开始，而不是最后一周，原因在于晚写的测试实际上在做什么。

## 最后一周写的测试在检查什么？

最常见的错误是以为有了生产环境检查就够了。生产环境测试必不可少，但它只能说明系统依然健康，无法告诉你上一次改动破坏了哪个行为。

更隐蔽的错误是把测试都挤到最后一周。这时写的测试往往只是记录代码当前的行为。freeCodeCamp 的一篇教程说得很明白：这类测试并不能说明现有行为是正确的；作者写道，自己只是在记录行为，并不假装它是对的。

重构遗留代码时，这种技巧很有用。但如果交出去的测试套件全是这种测试，你就是把自己的错误冻结起来，当作预期行为交给客户团队。在你还在问客户“没有 ID 的行该怎么处理？”时就写下的测试，记录的是意图，而不是现状。

另一个陷阱是写一个巨大的测试，从输入文件一路检查到仪表盘。它一旦变红，客户工程师根本看不出是哪个行为坏了。最后一个陷阱是在现场修了缺陷却不留测试，还把修复一直放在定制分支里。缺陷可能卷土重来，核心产品也永远不会知道它的存在。

## 招聘经理想看到什么？

在简历上，“为客户 X 部署了一条数据管道”的分量不比一次顺利运行更重。“交接时为现场发现的每个缺陷都附上了回归测试；之后的修复由客户团队独立上线”说明的要多得多。

面试时，要准备好讲清一个具体缺陷：你是怎么复现的，测试长什么样，这个测试现在放在哪个仓库里。

现场顺利运行能帮你拿到验收签字。交接做得好不好，要等到客户团队第一次修改代码、而且不用打电话找你的时候，才见分晓。

**本周动手试试:**

- 打开你最近为客户写的定制代码，列出客户团队将来需要修改的三个行为。为每个行为写一个简短的测试，每个测试只检查一个行为。
- 挑一个你在客户现场发现的缺陷，把它变成回归测试，完整复现引发缺陷的那份输入。
- 下一个项目开始前，用数字写下“可用”的定义，并在动手写生产代码之前请客户确认。

## 信源

- [Testing for Reliability (Site Reliability Engineering, Ch. 17)](https://sre.google/sre-book/testing-reliability/)

- [Testing Overview (Software Engineering at Google, Ch. 11)](https://abseil.io/resources/swe-book/html/ch11.html)

- [The Forward-Deployed Engineer Playbook (DEV Community, Truong Phung)](https://dev.to/truongpx396/the-forward-deployed-engineer-playbook-23d9)

- [Field‑to‑Product: an operating system for Forward‑Deployed Engineers](https://secondfile.substack.com/p/fieldtoproduct-an-operating-system)

- [WTF is a forward deployed engineer? (and why everyone is hiring them)](https://posthog.com/blog/forward-deployed-engineer)

- [How to Build Characterization Tests Before Refactoring Legacy Code](https://www.freecodecamp.org/news/characterization-tests-before-refactoring-legacy-code/)

- [The Project Handoff (Forward Deployed Engineer for AI Systems, Educative)](https://www.educative.io/courses/forward-deployed-engineer/the-project-handoff)
