工程与系统

把一个过重的客户端页面拆成三层之后,项目终于能测了

当一个客户端页面同时承担数据编排、状态控制和展示细节时,测试会变成补救。先拆层,才有可能真正测得动。

页面一旦同时管太多事,测试通常已经输在起点

很多人看到一个客户端页面变得难测,第一反应会归因到测试工具、mock 复杂度或者环境配置。但这类问题往往在测试开始之前就已经决定了结果:页面本身把太多责任揉在一起了。它既决定启动流程,又处理恢复逻辑;既编排状态切换,又顺手把 loading、空状态、运行态、局部按钮行为和文案细节都握在一个地方。这样写在最开始确实很省事,因为改动时不用在很多文件之间跳转,但一旦进入“要长期演化、要补回归保护”的阶段,代价会立刻显现。

这个项目里的 WorkspacePage 就是很典型的例子。真正难的不是它文件长,而是你根本没办法只验证其中一段行为。你想测“恢复工作区”,会被 UI 分支拖住;你想测 loading 状态,会被会话恢复和持久化数据影响;你想测空状态,又得先绕过一堆和运行态有关的条件。测试写不动,通常不是测试工程不行,而是页面已经拒绝被理解。

真正有效的第一刀,不是先拆 JSX,而是先拆启动链路

我后来觉得这次重构里最重要的判断,是没有一上来先做视觉组件切分。很多重构会习惯先抽按钮、抽卡片、抽 section,看起来文件立刻变整齐了,但真正缠在一起的判断链路并没有被切开。对于 WorkspacePage 这种页来说,最先要拆的其实不是视觉,而是“它到底怎么进入不同状态”。

所以第一步真正抽出来的是 bootstrap 层,也就是后来的 src/components/workspace/workspace-bootstrap.ts。启动、恢复、读取持久化画布、读取预览草稿、决定是否要清理 stale session,这些都先被拉出页面层,变成一个可以直接被测试的流程模块。只有当这条链路被单独拎出来,页面总装层才第一次不必再同时承担“做判断”和“做展示”两种复杂度。

三层拆法真正解决的,不是文件大小,而是复杂度污染

这次最后形成的结构我很认同,因为它不是机械分文件,而是把不同类型的复杂度放回了更合适的位置。

第一层:bootstrap 层

这一层只回答一件事:用户现在应该进入哪种状态,以及进入该状态需要哪些初始化数据。它关心的是启动、恢复、远端 session、持久化画布和预览草稿,不关心最终显示成什么样。

第二层:shell 层

这层包括 WorkspaceLoadingShell.tsxWorkspaceRuntimeShell.tsxWorkspaceStarterCard.tsx。它们负责的是“在某种状态下应该呈现什么结构和交互”,而不是决定为什么会进入这个状态。这样一来,loading、starter、runtime 都能在更清楚的边界里独立演化。

第三层:具体运行区

真正和录制、白板、提词、模板以及运行态交互有关的细节,尽量留在运行层内部,而不是一路向外泄漏到页面总装层。这样页面不需要知道太多内部实现,运行层也不会被外部启动判断反复污染。

这三层的意义,不是让项目“更像架构图”,而是让流程复杂度、状态呈现复杂度和业务交互复杂度不再互相拖累。结构一旦清楚,很多之前写不动的测试就有了明确落点。

能测之后,真正改变的不是覆盖率,而是你终于敢继续改它了

我现在越来越相信,可测性的真正价值不是让报告更好看,而是让人敢动系统。之前 WorkspacePage 太重时,每次改它都有一种碰硬块的感觉:你知道它该变,但你无法确定改完之后 loading、恢复、starter、runtime 会不会一起被连带打坏。那种“不敢动”其实是比“暂时没测试”更严重的问题,因为它意味着页面已经失去演化弹性了。

拆层之后,情况才真正改变。我要改启动与恢复判断,就去 workspace-bootstrap.test.ts 这类流程层测试;我要调整空状态,就集中看 starter card;我要改运行态壳层,就只在 runtime shell 周围验证。这时候测试不再像事后补救,而像结构是否真的被拆开的证据。只要某段逻辑还无法被单独验证,它大概率就还没回到正确层级。

这次重构里,我最想保留下来的三个判断

先拆判断链路,再拆视觉组件

如果流程还缠在一起,视觉组件拆得再细,系统也只是看起来更整洁,本质复杂度并没有下降。

页面过重往往不是因为功能多,而是因为职责没分层

启动、恢复、状态编排和展示细节被混在一起,才是难测的根源。功能数量只是表象。

测试不是重构完成后的收尾动作,而是结构是否成立的验尸报告

这次之所以真正让我满意,不是因为文件名字变好看了,而是因为后来确实能用测试去验证启动链路、恢复逻辑和状态落点。这说明拆层不是形式动作,而是责任边界真的被挪回了更合理的位置。

对一个还在持续迭代的产品来说,这种变化比一次局部视觉优化重要得多。因为它意味着页面终于从“只能勉强维持”的状态,回到了“还可以继续长”的状态。