2019年,一个在网易游戏做引擎开发的工程师在他的季度架构评审日上做了一件让妻子觉得“你是不是有焦虑症”的事情。他把家里所有资产和负债列在一张Excel表上,然后开始手动输入各种极端数字——房价跌百分之四十、沪深300再跌百分之三十、他被裁且十二个月找不到同级别工作、他妻子所在的外企突然退出中国市场。他妻子路过书房的时候瞥了一眼屏幕,问他是不是在写灾难小说。他说不是,是在做系统压测。
2022年,他所在的游戏部门因为版号停发和政策调整被迫裁员,他妻子所在的外企确实开始收缩中国区的业务线。他们家在同一季度里失去了一份半的主动收入。他后来跟我说,他在那个季度里唯一没有焦虑的事情,就是他早在三年前就已经算过,知道最坏情况下他们家能撑二十三个月。他说当人力部门把离职协议推到他面前的时候,他脑子里想的不是“我完蛋了”,而是“这个数字和我三年前那版脚本里假设的离职补偿金额只差了五千块”。他签完字回家,打开那版压力测试脚本,把实际数字更新进去,然后给妻子发了一条消息:和预期一致,按预案执行。
脚本结构设计:输入变量、计算逻辑、输出结果,缺一不可
你写过的任何一个像样的测试脚本,都有三层结构:定义输入,执行逻辑,校验输出。压力测试脚本也不例外,但它的特殊之处在于,输入变量不是你平时测试策略时用的历史收益率序列,而是你家庭资产负债表上每一项资产和负债在极端条件下的模拟价值。计算逻辑不是在回测你的择时胜率,而是在模拟你的现金流在多重极端冲击下从正常运转到彻底断裂的完整时间线。
输入变量层是你需要自己定义的最关键部分。你不能只测一个“股市跌百分之三十”的单变量场景,因为真实世界的极端事件从来不是单变量冲击。2008年是股市暴跌叠加失业率飙升,2020年是公共卫生危机叠加全球供应链断裂,2022年是中概股崩塌叠加互联网行业大裁员。每一次真正的危机都是多个变量同时向你最脆弱的方向冲击,而你的压力测试脚本必须模拟这种多变量并发的场景。
你至少需要设定以下五类输入变量:金融资产跌幅,包括你的股票仓位、指数基金、行业ETF、以及任何和股票市场挂钩的理财产品,在压力场景下你给它们分别设定跌幅假设,个股跌幅应该远大于指数跌幅,而且要注意一个你在平时不会注意到但在崩盘时必定出现的现象——所有资产之间的相关性在极端尾部会迅速趋向于一,你以为分散在不同行业、不同市值风格、不同地域的持仓能互相对冲,但在真正的系统性危机中它们会一起跌,你的分散化只能保护你在温和回撤时的体验,保护不了你在踩踏时刻的绝对净值;不动产跌幅,自住房和投资性房产各自设定一个你认为在本轮周期内可能出现的最大回调幅度,同时还要考虑如果房贷利率跟随基准利率飙升,你的月供利息部分会发生什么变化;人力资本冲击,也就是你和你家庭其他主要劳动力的主动收入可能遭受的损失幅度和持续时长,失业、降薪、被动转为兼职或自由职业但实际收入大幅下降,这些情况都要被量化成具体的月收入数字和恢复周期假设;通胀和生活成本,在真正的极端场景里物价可能出现你不熟悉的涨幅,CPI的温和波动和你家真实感受到的生活成本上涨往往不是同一个数字,你需要给你家庭的月度硬性支出设定一个压力情景下的增长率;以及负债利率,如果你持有浮动利率的负债——无论是挂钩LPR的房贷还是其他浮动利率的借款——你必须假设一个利率飙升的场景。
计算逻辑层要完成的事情,是把这些输入变量按照真实的因果关系串联起来,计算出一条你家庭净现金流随时间变化的曲线。你的月度现金流入包括主动收入、被动收入(股息债息租金)以及你已经锁定的离职补偿或失业保险金。你的月度现金流出包括全部硬性支出——房贷或房租、生活基础消费、保险续费、子女教育、以及压力情景下可能新增的医疗支出或应急支出。净现金流入减去净现金流出,就是你家每个月的资金池增量。当增量持续为负时,你的应急储备金开始被消耗。应急储备金归零的那一天,就是你系统的崩溃点。在归零之前,你需要标注出几个关键节点:你被迫开始赎回固收类资产来补充流动性的时刻,固收类资产耗尽、你被迫开始卖出权益类资产(不管那时股价跌了多少)的时刻,以及你被迫开始出售非流动性资产(车、投资性房产、甚至自住房)的时刻。每一个节点的触发时间,都是你系统设计质量的最诚实评分。
输出校验层是整个压力测试脚本里最让你难受但也最重要的部分。你不能输出一个模糊的感受——“我感觉我们家应该扛得住”。你必须输出一个精确到月份的崩溃时间。你在所有触发条件下,家庭现金流断流的最早时刻是哪个月?那个时刻你还有多少未变现的资产但无法及时转成现金?你被迫割肉的那个月,你的权益类资产市值比你买入成本跌掉了百分之多少?如果这些问题的答案超出了你能承受的底线,那你的系统架构必须在压力测试之后被送回第二章的架构评审会,重新调整内核层厚度、系统服务层比例和应用层沙箱配额,然后重新跑一次压测,直到崩溃时间落在你能接受的底线之后。
用Excel就够,真正的挑战不是计算,是诚实
读完上面那段,你可能会觉得这个脚本需要搭一个Python环境,跑蒙特卡洛模拟,做几千次随机采样才能算出结果。不需要。你的家庭资产负债表上的条目数量,大概率用不了一张A4纸的正反两面。你的资产类别——现金、固收、权益、不动产——四个大项,每个大项下面小项加在一起可能只有十几二十行。Excel完全够用,而且是比任何编程语言都更适合做这件事的工具。因为在Excel里,你每一个单元格的公式都可以被你妻子、你父母、或者任何一个不写代码但需要参与你家庭财务讨论的人点开看,看你的计算逻辑是不是合理,看你的假设是不是过于乐观。透明本身就是一种纪律。
压测脚本真正的难度从来不在计算上。你有能力手写一个分布式一致性协议,你的计算能力远超这份脚本需要的全部数学。压测脚本真正的难度在你输入那些假设数字的那一刻。
你敢不敢在“失业后重新找到同级别工作的恢复周期”那一栏里写十二个月?你敢不敢在“自住房产最大回撤”那一栏里写百分之三十?你敢不敢把这两个数字放在同一张表里,然后让公式算出来的结果显示给你和你配偶看——你们家在第十一个月之前就会耗尽全部流动资产?
大多数人不写压测脚本,不是因为不会写,是因为不敢看输出结果。你写代码的时候从来不回避压测——你主动把QPS拉到集群极限,你主动往数据库里插入脏数据,你主动拔掉一台核心服务器的网线然后看监控面板。你做这些事的时候,心态不是恐惧,是专注。因为你清楚,你现在主动制造故障,是为了将来被真实故障突袭的时候你不需要在恐惧中临时做判断。
把你对待线上服务的工程师心态,用在对待你家庭财务的压测脚本上。找一个周末下午,关掉手机,泡一杯茶,打开Excel,诚实地输入那些你一直在回避的假设数字。让公式跑出结果,盯着那个崩溃月份的数字看几分钟。如果太短,不要恐慌——你还有时间。回到前面的章节去加固你的内核层,调整你的资产配置,增加你的保险覆盖,延长你的应急储备金水位。然后下个季度重新跑一次。直到崩溃月份变成一个你即使在最坏情况下也能坦然面对的数字。
定期回归测试:重大变更之后必须重跑全量
你在CI流水线里配置自动化测试的时候,一定加了一条规则:每次提交代码,全量单元测试必须重新跑一遍。不是因为你每次改的代码都会把原有功能搞坏,是因为你不知道哪次修改会把原有功能搞坏。而在你不知道的时候,让机器替你跑一遍所有测试,比你上线之后用户替你发现Bug便宜得多。
你的家庭压力测试脚本,就是你家庭财务系统的全量回归测试套件。触发回归测试的条件不是时间——不是你设定一个日历提醒每年跑一次。触发回归测试的条件是资产量级跃迁和人生阶段切换。
你的资产每增加一个量级——你的总净资产从五十万跨过一百万,从一百万跨过三百万——你原有的压力测试假设就不再适用了。一百万的时候你的应急储备金覆盖十二个月生活支出是一笔数字,三百万的时候你的生活支出可能已经因为换房、生娃、子女教育而同步膨胀了,那笔应急储备金还能覆盖十二个月吗?你需要在净资产每跨过一个显著台阶的时候,重新坐下来,把压力测试脚本里和支出相关的所有输入变量全部更新一遍,重新算出那个崩溃月份。
人生阶段的每次切换也是回归测试的强制触发条件。你结婚了,你的家庭财务系统从此不再是单核处理器,你的配偶的收入和支出要全部纳入脚本,你们两个人的主动收入同时中断的概率远低于一个人,但你们两个人的生活支出也高于一个人,两条新变量的引入必须被完整测试。你买房了,你的负债端多了一笔长达三十年的硬实时任务,月供就是你的债务时钟,脚本里必须新增一列输入变量专门模拟利率变化对月供的压力。你生娃了,未来十八年甚至更久的抚养和教育支出不是一次性计入,而是作为一条持续增长的支出曲线叠加到你原有的月度消耗上。
每一次回归测试跑完,你的崩溃月份可能会变短——因为你的生活更复杂了,你的责任更多了,你的支出更刚性了。这不可怕。可怕的是你生活已经变了,但你的压力测试脚本还停留在你单身时那个只有租房支出、没有负债、一人吃饱全家不饿的旧版本里。用旧版本的测试结果来评估新版本系统的安全性,是你职业生涯里绝不会犯的低级错误。别在你自己家的事情上犯。