5000万行Ruby代码,每次提交跑完全部测试要多久?Stripe的答案是:90%的测试根本不用跑。
这不是偷懒,是他们花了三年打磨的一套选择性测试执行系统。Ruby基础设施平台负责人Aditya Anchuri最近披露了细节——这套系统让CI时间从"泡杯咖啡等结果"变成了"眨个眼就出反馈"。
从"全量跑"到"精准砍":一场持续三年的手术
2019年的Stripe,代码库还没现在这么庞大,但工程师已经嗅到危险。每次CI全量跑测试,资源账单像雪球一样滚。更麻烦的是反馈慢:开发者提交代码后,去倒杯水、回几封邮件,回来一看还在跑。
团队试过简单粗暴的解法——按目录切分测试,只跑改动相关的模块。Ruby的动态特性让这条路走不通:一个方法的改动可能通过元编程(运行时动态生成代码的机制)影响完全不相干的文件。
Anchuri的团队换了个思路:与其猜哪些测试该跑,不如让代码自己"交代"依赖关系。
他们造了一套静态分析工具,在编译期(代码转机器码的阶段)扫描整个代码库,建立文件级别的依赖图谱。图谱精度到什么程度?连某行代码修改会触发哪些测试,都能提前算出来。
2021年第一版上线,砍掉约60%的冗余测试。工程师没满足,又花了两年把覆盖率提到90%以上。
两个隐藏陷阱:缓存失效与"幽灵依赖"
选择性测试最怕两件事:该跑的没跑(漏检),不该跑的跑了(浪费)。Stripe踩过两个深坑。
第一个坑是缓存污染。他们的系统会把分析结果缓存加速,但Ruby的require机制(动态加载代码的方式)让依赖关系可能随运行环境变化。团队最终采用"内容寻址缓存"——以文件内容为key,而非文件路径,彻底规避环境差异导致的误判。
第二个坑更隐蔽。有些测试看似和改动无关,实则通过全局状态(比如数据库schema变更)互相影响。Stripe的解法是给这类测试打标签,强制纳入全量执行池。代价是多跑5%的测试,换来零漏检的确定性。
Anchuri提到一个细节:系统上线后,工程师开始反过来依赖它——有人故意把测试拆得更细,因为知道CI负担不会线性增长。
50M行代码的启示:快不是目的,信任才是
这套系统的真正价值,Anchuri认为是"反馈循环的质变"。
以前CI慢,工程师倾向于本地多测几遍再提交,反而拖慢整体节奏。现在CI快成基础设施,提交频率上去了,小步快跑成为默认工作流。数据上,Stripe的日均提交次数在系统成熟后翻了将近一番。
技术层面,他们的依赖分析引擎已经开源为Sorbet生态的一部分。但Anchuri提醒:工具可以抄,组织习惯抄不了。
Stripe能做成这件事,前提是代码库长期保持模块化——50M行Ruby没有烂成一锅粥,靠的是持续的重构投入和类型系统(Sorbet)的约束。没有这些底子,选择性测试就是沙上建塔。
国内大厂近年也在推类似方案,但多数卡在"历史债务":代码耦合度高,静态分析根本画不出清晰的依赖图。Stripe的路径未必可复制,但至少证明了一件事:CI优化的天花板,早在写第一行代码时就决定了。
Anchuri在文末留了句话:「我们现在想的是,怎么让剩下的10%测试也跑得更快——也许不是砍,是让它们值得被跑。」
你的团队CI要跑多久?有没有算过里面多少比例是在"陪跑"?
热门跟贴