一个表单通常用这样的值验过:一个普通姓名、一个真实存在的日期、一个放得下的数字。然后它遇到真实数据——有人粘贴的名字末尾带空格,有人所在国家的两位代码恰好是 NO,有人把导出的表格用电子表格软件打开。缺陷报告于是从用户那里一条一条地回来,时间远在代码写完之后。
这些取值既不秘密也不 clever,它们是一张短而稳定的清单。之所以长期没被测到,原因很 mundane:没人记得全,手工敲进去又太麻烦。把这件事工程化的思路很值得借鉴——不是再写一份"坏字符串列表",而是把取值、它通常破坏什么、正确行为应该是什么打包成一个可执行的目录,并让它能在任意输入框里按固定顺序一键送入。
目录里到底有什么
一套已公开的开源目录把 246 个取值组织成 20 个包,每个值都带两个说明字段:breaks(通常破坏什么、为什么)和 expect(正确实现应该怎么做)。缺了 breaks 字段的值在校验时会被直接拒绝——因为这张目录存在的意义正是解释,而不只是罗列。
| 类别 | 它针对的失效面 |
|---|---|
| 空白字符 | 占位置(或声称占位置)却在界面上完全看不见的字符 |
| Unicode 文本 | 看起来无害,却破坏计数、比较与显示 |
| 长度炸弹 | 按常见上限构造,以及字符数与字节数之间的落差 |
| 魔法值 | 人读成文本、解析器读成布尔值/空值/数字的词 |
| 极端数字 | 类型边界、外来记数法、精度耗尽的点 |
| 不可能日期 | 不存在的、有歧义的、写法与字段假设不同的日期 |
| 导出破坏者 | 通过校验、只在导出文件里出问题的值 |
| 本地化标识 | 全球商用产品处理不了的特定语言标识符与格式 |
| 文件名与路径 | 文件系统、压缩包或服务端会拒绝、改写、误读的名字 |
| 同形对 | 屏幕上相同、存储里不同的成对取值 |
| 复杂 emoji | 基本平面之外的字符与组合序列,破坏计数与截断 |
| 编码乱码 | 经历过错误编码且再也回不来的文本 |
| 从文档粘贴 | 从字处理软件或网页复制时附带的字符 |
| 邮箱与 URL | 形式上合法但极端的地址,以及只是看起来合法的地址 |
| 姓名与身份 | 打破"正常姓名"假设的名字 |
| 时区 | 不存在的小时、出现两次的小时、没写时区的时刻 |
| 货币与分隔符 | 人与电子表格书写金额的方式,以及精度终点 |
| 伪本地化 | 把国际化测试常用技术压缩成可直接粘贴的取值 |
| JSON 载荷 | 解析器与数据库读法不一致的请求体,可改字段名套用到自己的 API |
| 注入检查 | 引号、标签、模板表达式、路径等经典转义检查,被单独标记为敏感 |
有两个设计细节很实用。过长的值不直接存成字符串,而以"配方"形式保存、发送时才展开,因此一个包能用几行容纳十万字符的长度炸弹;必须保持领先的时间值同样以规则保存,所以时区类始终送入下一次时钟调整,而不是一个已经过去的日期。

二十类取值按"输入 → 校验 → 存储 → 导出"链路分别命中的失效面
先试这八个:取值、后果与正确行为
如果只能抽十分钟,下面八个取值覆盖了最常见、代价也最高的一类遗漏。左侧是取值,右侧是它通常打中的地方,以及正确实现应有的行为。
| 取值 | 通常破坏什么 / 正确行为 |
|---|---|
Kowalski 加一个尾随空格 | 浏览器与服务端的比较口径不一致,账号建成了却查不到;应在所有环节统一裁剪,或所有环节统一保留,不能注册一套、登录一套 |
=1+1 | 电子表格把它当公式,导出文件显示计算结果而不是数据;导出时必须按目标格式转义 |
no | YAML 1.1 与若干配置解析器读成布尔 false,而 NO 同时是挪威的国家代码;类型必须显式声明,不能靠猜 |
30.02.2026 | 朴素解析器把它滚到 3 月 2 日而不是拒绝;应直接判为非法日期 |
| 单个零宽空格 | 字段看起来是空的、实际非空,必填校验被绕过;应在校验前做归一化 |
| 128 个字符但占 256 字节 | 界面按字符数放行,存储按字节限长拒绝,或反过来;前后必须统一计数口径 |
007 | 电子表格把该列当数值后立刻丢掉前导零;导出时应保留文本类型 |
| 65 535 个字符 | 常见文本列类型的上限,越界通常直接抛原始数据库错误;服务端应显式校验并给出可读的错误 |
这里最能说明问题的是 30.02.2026 和 007:它们不是"恶意输入",而是完全正常的真实数据,只是恰好落在实现者的假设之外。也正因为如此,靠"随机 fuzz"未必能稳定命中,靠一张有解释的目录反而更快。
让它变成一轮十分钟的手工测试
目录的交互设计围绕一个目标:不打断测试者的注意力。
- 一个组合键清空当前行、把当前包的下一个值送进焦点所在的输入框;上一个值、回到包首各有对应组合键,全部可改;
- 显示面板不抢焦点,光标始终留在被测字段里,值不会被打进面板自己;
- 不可见字符在面板里画成一个可见标记(如
␣),同时按四种口径计数:字素簇、码点、字节、UTF-16 单元; - 面板显示下一个值以及当前在包里的进度,另有一个检索窗口可跨全部包查找;
- 发现问题时,一个组合键复制一段可直接粘进缺陷单的报告块:哪个值、来自哪个包版本、以什么形式送入、多大、是否完整到达;值以"别人能重新敲出来"的形式写出,而不是面板上那个占位标记;
- 图形界面与命令行共用同一套引擎,命令行同样支持剪贴板模式,把值放到剪贴板由测试者自己粘贴。
每个值自带 breaks 与 expect,是这套流程和"一份纯文本清单"最本质的差别:测试者不必先回想"零宽空格会怎样",面板、命令行查看、报告块三处都会直接说明。于是探索性测试从"记得什么就试什么"变成"按固定顺序走一遍",覆盖可复现,也更容易交给新人。
接进自动化:别把清单拷进数组
图形界面适合探索,自动化需要的是同一份数据的机器可读出口。目录工具提供把任意包导出为 JSON、CSV、每行一个值的能力,可选转义或原始形式、可选 base64;诊断信息写到标准错误,因此重定向出来的文件只有值,可以直接当测试数据文件用。
一个典型接法是把"魔法值"包导出成逐行文本,再用参数化测试把它们喂给同一个校验函数:
| |
把 breaks 直接放进断言失败消息里,缺陷单的描述几乎不用重写;expect 则适合沉淀成团队的用例模板字段。相比在测试文件里硬编码一长串字符串,目录升级时只需重新导出一次。
命令行送入模式还演示了边界处理应有的克制:执行前给几秒让测试者点中目标字段;按住 Ctrl/Alt/Shift/Win 时不输入,目标窗口权限高于自身时不输入,焦点落在按钮、链接、列表项上时不输入,按下 Escape 立即停止,这些情况统一返回一个专门的退出码而不是"猜一下"。自定义包用 TOML 描述,配套的 lint、fmt、new-pack 子命令负责校验格式、规范化与生成骨架——其中校验器目前覆盖 47 条格式规则中的 41 条,剩余几条会明确列出原因。

同一份取值目录同时驱动手工探索、参数化测试与缺陷报告
诚实地说明边界
一份会主动列出自身限制的工具说明,比一份只讲能力的更值得信。这套做法的边界有六条,评估是否引入时都要算进去:
- 它不判断结果。 只把值送进控件,不读取窗口显示了什么,是否处理得当由人判断;结果标记功能尚未提供,报告块就是唯一的记录。
- 系统可能改写输入。 某些操作系统的文本替换会把送入的值改成另一个值,工具在检测到相关设置开启且可能影响当前值时会给出提示。
- 跨平台能力不对称。 向其他窗口送键需要系统授权:某平台需要无障碍权限,另一平台在 Wayland 下需要输入法守护进程且无法确认焦点是否真的落在输入框;缺少条件时,工具明确报错并退回剪贴板模式,而不是静默降级。
- 自定义包目前可写不可加载。 校验、格式化、生成骨架都可用,但把自己的包目录读进面板与命令行尚未接通;包格式也还没冻结,后续版本可能变更。
- 246 个值只是一个起点。 它精选的是"漏掉代价最高"的那批,不是任何意义上的完备清单。
- 它不是攻击工具。 绝大多数取值来自真实用户与真实数据;唯一包含经典转义检查的包被单独标记为敏感,其中没有针对已知漏洞的利用代码,且任何输入都必须由人主动触发。用途是验证自己负责的系统。
国内团队可以这样落地
直接照搬英文包会漏掉本地场景,建议按下面的方式改造成自己的目录:
- 把 20 个类别逐一映射到本系统的字段清单,明确每类由哪个页面、哪个接口负责验证,避免"目录有了但没人认领";
- 优先覆盖跨系统边界:导入导出、表单到数据库、服务端到报表、开放 API 到下游,这几处是计数口径与类型假设最容易分叉的地方;
- 把
breaks/expect两个字段写进团队用例模板,让每条边界用例都必须回答"破坏什么"和"正确行为是什么"; - 把包导出为 CSV 或 JSON,作为参数化测试的唯一数据源,目录更新时重新导出,不在多处维护副本;
- 补充中文本地化取值:全角空格、姓名长度与生僻字、含 emoji 的昵称、手机号与工号的前导零、证件号末位的
X、金额的千分位与全角符号、以及中文排序与大小写无关场景; - 把发现的新取值按"值 + 破坏什么 + 在哪见过"的格式贡献回团队目录——一个让某人损失过一下午的输入,值得让所有人只损失一次。
真正需要改变的不是工具,而是测试数据的地位:它不该是随手编的几个"正常值 + 一两个空值",而应当是一份有解释、有顺序、有版本、能同时驱动手工探索和自动化套件的资产。表单没见过的输入,用户迟早会替你送进去。
