图片
图片
作者 | Shantnu Tiwari
译者 | 核子可乐
策划 | 钰莹
这篇文章要跟大家分享的是一个因为代码 / 测试脚本出错而引发严重后果的故事,同时也希望广大研发人员以此为戒,一定要认真测试代码,并且即便是测试脚本也值得与普通代码获得同样的关注和重视。

文章开篇,首先强调一句毫无营养的废话:一定要写单元测试。

好的,接下来是我的血泪故事。

恐怖脚本之夜

这段经历发生在周五晚上 17:15。当时,我正打算下班欢度周末。一位同事突然发消息问我:“是否还记得昨天写的脚本?它已经成功冻结了南美几千部手机,用户已经爆炸了,咱们可能要丢饭碗。”

内容不长,但我已经感到害怕了。疫情当前,我只是个没什么保障的外包,一旦丢了工作会很麻烦,而眼前最重要的问题是弄清楚这个脚本是如何冻结几千部手机的。

我在一家安全公司的测试自动化部门工作,生产的主要产品是一款应用程序,负责帮助移动运营商在发现手机被盗或合约客户欠费时对设备进行冻结。这款应用程序被直接嵌入在 Android 操作系统之内,所以用户没法卸载。在冻结之后,手机会丧失绝大部分基础功能,包括拨打电话、使用 Wi-Fi 甚至在 Instagram/Facebook 上发布图片……总之,只要不续费,手机基本相当于废了。

这些还算正常。我们公司也给电信运营商开发了前端,用来检查哪些手机即将被冻结。这些工具在内部环境运行良好,我也为自己几乎一己之力承担的这个项目感到自豪。

遗憾的是,一家总部位于韩国的手机制造商(这里我们用化名称其为 K-Pop 公司)特别喜欢用我们的软件,想把这些成果跟他们蹩脚的 Web 工具融合起来。我得到了报酬、也充分测试过他们提供的用例,感觉没什么问题。

那么,到底是哪里出错了?

早已注定的悲剧

我们公司之前刚被投资机构收购,他们想尽快让投入变现。于是,所有项目的 deadline 都被提前,我们甚至同时测试过三款有着完全不同要求的产品。

换句话说,我们完全是在碰运气,祈祷一切都能顺利运行。而最后一项测试内容,就是保证在移动运营商通过多部手机上传一个 Csv 文件时,将所有手机同时冻结。

我写了一个 Python 脚本,它会生成一些随机的手机号码、登录至 Web 门户并冻结手机。之后,脚本登录至另一门户并检查结果。如此一来,我们就能用一个脚本测试数以万计的组合,并在几个小时之内完成全部测试。

因为我们面对着多个不同的用例 / 场景,所以这套脚本最终生成了数十万个随机手机号码。

说到这里,大家应该可以想到出了什么问题。

由于时间压力太大,我没空也没心情检查脚本质量。代码一写完,我就把它们运行起来。事实上,在按下保存按钮的 10 秒之后,脚本就已经在实时生产服务器上跑开了。我其实可以再多测一测,但那样我就得加班到半夜。我不想加班,因为我已经加过好多班,错过好多周末,我得赶紧把差事脱手。

于是我运行了脚本,一切正常。客户方经理确认效果与预期相符,每个人都很开心。周末正常休息,下周一产品就能上线。

之后我就收到了邮件:我们在南美错误地冻结了几千部手机。

怎么回事?

之前提到过,测试用的手机号码是“随机”生成的;也就是说,这个 Python 脚本会随机生成包含 11 个字符的“手机号码”。

这些号码跟现实中的号段碰上了!

受到脚本编写方式及某些奇怪 IMEI 设定(即每部手机的唯一识别编号)的影响,所有冻结用户都集中在南美。

解决问题

想清楚这一切之后,我们开始着手解决问题。同事问道:“你测试时用过的 Csv 文件还在吗?”我们可以再次运行脚本,把里面的冻结操作改成解封就行了。

当然……没有了!原始脚本被改过,覆盖了,我手上只有最后一份副本。毕竟我原以为这就是个一次性的快速测试,所以没做准备。

还有另一个解决办法:登录这家韩国 K-Pop 公司并手动下载列表,但对方只支持一次下载 100 个号码,而我需要下载 10000 多个。

于是,我只能编写一个脚本专门下载手机号码,再写另一个脚本清理对方提供的糟糕 Csv,再用第三个脚本负责重新接入服务器并解封所有受影响的手机。

多亏同事伸出援手,所以我们大约一个小时左右就完成了所有工作。到晚上 18:30,我们已经解封了所有手机。一位项目经理也证实了工作成果,我松了一口气,并迅速逃离了现场。

学到的教训
  • 测试脚本需要像常规代码那样得到关注与维护。
  • 无论管理层怎么考虑,开发者都不该在 deadline/ 高压之下测试关键生产代码。(我也知道,想完全无视高管的指令几乎不可能;要么照指示干活,要么准备走人……)
  • 这家 K-Pop 公司权力很大——他们的系统注册过程相当难受,但注册之后获得的权限却高得离谱,甚至能冻结世界上任意地区的手机。

K-Pop 公司可以根据 IMEI 编号判断哪家运营商“拥有”哪部手机,所以需要承担相应的法律责任。如果是从 T-Mobile 那边买到的手机,那么应该只有 T-Mobile 有权冻结手机,除非客户后续履行完合约责任或者变更运营商。

但是,K-Pop Central 居然能在知晓对方手机号码或 IMEI 的情况下冻结任何设备(这两项都不难获取,特别是手机号码,毕竟这是要公开提供给其他人的信息)。教训就是:好好做检查,我该检查,K-Pop 更应该检查!

  • 测试应该分步进行。应该先测试各个组件,而不是直接测试最终系统。要说理由,因为我们没那么多时间。
  • 简单测试,或者说只考虑“最佳情况”的测试远远不够。在理想情况下,我们应该想到还有那些打算蓄意冻结他人手机的“坏家伙”或者是流氓脚本。这不只是在保障质量,更是在保障安全。但是我们哪一点都没做到。
  • 我们之所以都没做到,是因为我们以为 K-Pop 的手机软件会跟我们一样做测试。没想到人家根本不在乎,所以双方疏漏加起来造成了这次事故。
  • 别在生产环境里做测试!其实我们有开发与登台服务器,但还没有接入实时应用(主要受到时间和人手的限制)。另外,距离产品正式发布只有两、三天了,想四平八稳搞测试真的很难。

于是,我们压根没有吸取任何教训。在这款产品正式发布之后,我们又回归到糟糕的脚本中,在数据库中倾倒垃圾并假装已经“测试”过所有代码。

最终,我们没有被解雇,这是因为我们很快就解决了问题。

但更重要的是,其他一些项目经理和销售主管一直在犯同样的错误——随机生成手机号码并冻结真机。当然,他们只是手动操作,所以冻结的只有几部手机;我们用脚本一下子冻结了上万部。不过最终效果是一样的,大家心照不宣而已,每个人都假装这是个纯粹的意外。

Reddit/HN 上部分评论的回复:

我在 Reddit 和 HN 上看到不少类似的评论,所以这里统一做出回复。

  1. 没错,我知道在生产环境下做测试很蠢。但当时我们需要在三天之后的星期一发布产品,而且没有任何商量余地。前任项目经理就因为手脚不够麻利被解雇了,整个测试部门都面临被裁的风险。副总裁还要求开发者在每日例会上随时上报最新进度。

我之前也说过,这时候要么按上头的指示作事,要么准备卷铺盖走人。如果不是新冠疫情的压力,我真的会考虑换份工作。

  1. 为什么不能在登台 / 开发服务器上测试?我们没时间、也没人手让这些服务器正常起效。已经有多位工程师决定离职,而且我们也想不到 K-Pop 这样的大公司能冻结那些远在天边的手机。
  2. 对于不少朋友的疑问:这类操作是完全合法的。毕竟合约机在还清尾款之前,手机的所有权其实并不在用户那边,企业当然有权随时冻结。iPhone 上就有内置的功能,Android 则需要配合外部应用达成同样的效果。
  3. 虽然现在回想起来,整件事确实非常愚蠢(没错,自称工程师的我居然会在生产环境中做测试),但当时一波又一波的压力让人身心俱疲,根本没时间做什么反思或者规划。总之,这可能是很多人职业生涯中前所未有的高压时刻。

后续更新:在发布了三款产品之后,整个 QA/ 测试部门都被裁掉了。包括我的老板。有趣的是,我因为比较清楚遗留产品细节而留了下来。但三、四个礼拜过去,我一直处于没有顶头上司、不知道该向谁报告的诡异状态。

Loading

作者 aiforum

发表回复