打开网易新闻 查看精彩图片

去年11月,我花两周把单个包塞进Bazel。当时觉得"差不多懂了"。三个月后,整个仓库的测试全跑通了,才发现那次只是预习。

今天说测试。Bazel的测试哲学和前端圈惯用的那套,根本是两码事。

沙盒不说谎,但你的依赖清单会说谎

沙盒不说谎,但你的依赖清单会说谎

改造前,components包的test脚本是直接调Jest。这种跑法有个隐藏福利:整个node_modules对你敞开大门。只要文件还在磁盘上,require就能找到,管你有没有在package.json里正经声明。

改成bazel test :jest之后,第一次跑就崩了。报错三连:

Cannot find module '@testing-library/jest-dom' from 'jest-setup.js'

Cannot find module 'prop-types' from 'src/Pagination/index.tsx'

Could not locate module ./index.scss mapped as: identity-obj-proxy

三个包明明躺在node_modules里。Bazel不是瞎子,是故意装瞎——它给测试造了一个Linux沙盒,里面只放你显式写在data属性里的东西。没名单?就当不存在。

这三个依赖一直是"隐形人口":@testing-library/jest-dom在jest-setup.js里被引用,prop-types是组件的peer dep,identity-obj-proxy负责把SCSS模块换成mock对象。以前它们蹭着node_modules的顺风车,现在被Bazel的安检拦下来了。

修复倒简单。在packages/components/BUILD.bazeljest_test规则里补三行:

":node_modules/@testing-library/jest-dom", # used in jest-setup.js

":node_modules/prop-types", # peer dep of the component

":node_modules/identity-obj-proxy", # SCSS module mock

再跑,PASS。六个包如法炮制,又揪出几组漏网的peer dep和测试工具。

从"各自为战"到"一键过筛"

从"各自为战"到"一键过筛"

单包通了,下一步是根目录。老办法是pnpm -r run test,每个包起一个Jest进程。能跑,但Bazel的缓存和并行全浪费了。

新命令一行:

bazel test //packages/... --test_tag_filters=jest

//packages/...是Bazel的通配符,扫遍packages目录下所有测试目标。但仓库里还有:lint:stylelint目标,不加过滤器会全跑出来。

让每个jest_test规则带个标签,七个BUILD文件各加一行tags = ["jest"]根目录的package.json最终变成:

"test": "bazel test //packages/... --test_tag_filters=jest"

跑一遍,缓存命中、并行调度、失败重试,全是Bazel原生能力。以前七个Jest进程在各自小黑屋里闷头跑,现在被Bazel统一编排,快了多少没测,但CPU风扇声明显变了。

测试即文档,依赖即契约

测试即文档,依赖即契约

这套改造逼我做了一件事:把隐式依赖全部显式化。以前代码能跑就行,现在每个测试工具、每个mock、每个peer dep都得在BUILD文件里签字画押。

代价是多了几十行配置。收益是——半年后回来改代码,不会疑惑"这个jest-setup.js到底依赖了谁"。沙盒里的失败清单,就是一份精确的依赖地图。

有个细节:Bazel的测试缓存只认输入文件的哈希。如果你改了测试代码但忘了更新BUILD文件里的依赖声明,本地能过,CI会挂。这种"本地CI不一致"以前要查半天,现在沙盒帮你提前暴露。

下一步是把这个模式铺到E2E测试。但那是另一个故事了——毕竟,Cypress的沙盒策略和Jest还不太一样。