这个项目最特别的测试难点是:我没法自动在 Touch Bar 上点按、长按、滑动,它还会在空闲一段时间后熄灭,熄灭时用截图命令截到的是全黑。

这一篇先讲一次很有代表性的误判,再讲最后形成的三层验证方法。

一次黑屏误判

第一版做好之后,运行,截图,Touch Bar 是全黑的,连右边的按钮都没有。我先加日志,发现菜单项创建了、isVisible 是 true、列表里有 22 个图标,一切正常;怀疑布局约束冲突,把宽度改小、只留一个按钮,还是黑的。

直到我重新运行第一阶段那个确定能显示的探针,它也是黑的。那一刻才意识到问题不在代码。什么都不运行直接截图,同样全黑;读取键鼠空闲时间,已经三百多秒没有输入,Touch Bar 熄灭了。后来又发现屏幕其实已经锁了。

我还试过让它亮起来:用 caffeinate 保持唤醒,用合成的鼠标移动和 Shift 键事件,都没用。合成事件不会重置系统的键鼠空闲时间,Touch Bar 只认真实的输入。

这次误判留下的好处

这次误判浪费了不少时间,但也留下一个好处:因为第一阶段的探针是保留下来的,我才能立刻拿它做对照。这就是“先做最小探针”的价值,它不只是用来验证可行性,之后每一次“是不是代码的问题”,都可以先用它来排除。

三层验证:能自动测的自动测,能离屏看的离屏看,其余人工

第一层是自动测:私有接口探针检查接口是否都在,读取台前调度、Touch Bar 显示模式等相关设置;切换、隐藏、退出这些行为,用测试 App 和系统计算器实测,不碰用户正在用的窗口。

第二层是离屏看:写了一个渲染工具,用 App 自己的界面代码,把 Dock 画进一个 1004 乘 30 点的离屏窗口,再存成图片,完全不依赖 Touch Bar 是否亮着。README 里的示意图,也是用它渲染的,示例用的是系统自带的 App,不含任何个人信息。

第三层才是人工:单击、双击、长按的手感,睡眠唤醒和解锁之后自动恢复,这些只能真的去按、去合盖,写成一份发版前的检查清单,每次发版过一遍。

用辅助功能读自己的菜单

想知道菜单栏里到底显示了什么,不必截图。用辅助功能接口读 App 自己的菜单:找到状态栏图标,按下,逐项读出标题、勾选状态和子菜单,还可以用同样的方式点“English”“关于”。中英文两套菜单标题,就是这样一项项核对的,测完再切回“跟随系统”。

教训:先看空闲时间,再怀疑代码

以后遇到 Touch Bar 上什么都看不到,我的第一步是读键鼠空闲时间,其次确认屏幕有没有锁,最后才去查代码。一个已知能显示的最小探针是最好的对照:它也是黑的,就说明不是新代码的问题。

还有一处截图看不到的地方:左边的 Esc 键。截图命令截不到 Esc 那一块,所以“Esc 是否照常工作”只能靠实机使用来确认,我把它放进了发版前的人工检查清单。

自动化测试也有边界:能在没有 Touch Bar 亮着的情况下验证的,是接口是否存在、逻辑是否正确、界面版式是否正确;验证不了的,是真实的手感。诚实地把这两类分开,比假装全部自动化了更可靠。

其他值得记下来的坑

开着台前调度时,用截图命令去截一个新开的窗口,可能截到的是侧边栏里被缩小、倾斜的缩略图,要先确认窗口在前台再截。SwiftUI 的离屏渲染器画不出链接控件,会显示成黄色的禁止标志,而且因为没有 App 包,版本号显示成 dev,这两处都是渲染器的限制,不是 App 的问题。

报内存要用 footprint,而不是 RSS:ps 里看到的常驻内存是 103 MB,但里面包含了 AppKit 等系统库的共享页面,App 独占的物理内存只有 35 MB。还有一个:符号链接的 App,比如 Safari,直接取图标会带一个替身小箭头,取图标前要先解析成真实路径。