跳到主内容

一文搞懂正则边界用例设计与校验回归库搭建 ​

问题背景:正则出错,往往不是语法不会写 ​

很多团队把正则表达式当成“写完即走”的配置:需求是校验手机号,就从网上复制一段;线上出现误判,再临时加一个 | 分支。短期看改得很快,长期却会形成两类问题:一是合法用户被拦截,二是非法数据绕过校验。真正难的不是写出第一版正则,而是证明它在规则边界上持续正确。

正则校验的风险集中在边界:空字符串、首尾空格、全角数字、前导零、超长输入、大小写、换行符,以及业务允许但容易被忽略的格式。只测“一个正常例子”和“一个明显错误例子”,覆盖率通常很低。

因此,正则也应当像普通函数一样建立 regex-test:把规则解释成可执行断言,把边界用例沉淀为回归用例库。之后每次修改表达式,都能立即知道是否破坏旧规则。

方案对比:手写断言、表驱动测试与属性测试 ​

常见实践有三种。

第一种是手写断言,例如 expect(pattern.test('13800000000')).toBe(true)。优点是直观,缺点是用例分散,容易只测 happy path。

第二种是表驱动测试,把输入、期望结果、场景说明放入数组或 JSON 文件,循环执行。它最适合校验型正则,因为同一规则通常对应大量输入组合,且用例可以被产品、测试和后端共同维护。

第三种是属性测试,使用随机生成器构造字符串,再验证“不变量”。例如金额通过校验后,再用独立解析器确认数值范围。它能发现人工遗漏,但设计成本更高,适合身份证、金额、编码等高风险规则。

工程上建议采用“表驱动为主,属性测试补充”的方式:回归库负责固定已知边界,随机测试负责探索未知异常。

业务规则长度/字符/范围边界矩阵正例/负例/异常JSON 用例库版本化管理自动断言CI 回归失败输出:输入、期望、实际、规则编号修复后补充对应回归用例

实操步骤:从金额校验规则到可运行 regex-test ​

下面以“金额输入”为例:允许 0、0.00、12、12.5、9999.99;不允许负数、多个小数点、前导零、超过两位小数、超过 10000 元。

先写表达式:

javascript
// 可直接保存为 amount.test.js 后使用 Node.js 自带 test runner 运行:node --test
const { test } = require('node:test');
const assert = require('node:assert');

// 金额规则:整数部分 0 或 1~9999;小数部分省略,或 1~2 位
const AMOUNT_RE = /^(?:0|[1-9]\d{0,3})(?:\.\d{1,2})?$/;

function isValidAmount(value) {
  return AMOUNT_RE.test(value);
}

const cases = [
  // 正例:零、普通整数、边界最大值、不同小数位
  { input: '0', expected: true, note: '整数零' },
  { input: '0.0', expected: true, note: '零允许一位小数' },
  { input: '0.00', expected: true, note: '零允许两位小数' },
  { input: '12', expected: true, note: '普通整数' },
  { input: '12.5', expected: true, note: '一位小数' },
  { input: '9999.99', expected: true, note: '最大边界金额' },

  // 负例:长度、字符、小数点、前导零、范围
  { input: '', expected: false, note: '空字符串' },
  { input: ' ', expected: false, note: '空格不能自动通过' },
  { input: '01', expected: false, note: '禁止前导零' },
  { input: '1.234', expected: false, note: '小数不能超过两位' },
  { input: '1..2', expected: false, note: '只能有一个小数点' },
  { input: '.5', expected: false, note: '不能省略整数部分' },
  { input: '10000', expected: false, note: '超过最大金额' },
  { input: '-1', expected: false, note: '禁止负数' },
  { input: '12a', expected: false, note: '包含非法字符' },
  { input: '12\n', expected: false, note: '尾部换行不得混入' }
];

test('金额正则边界用例回归', () => {
  for (const item of cases) {
    assert.strictEqual(
      isValidAmount(item.input),
      item.expected,
      `${item.note}:${JSON.stringify(item.input)}`
    );
  }
});

逐段解析如下:

  • ^ 与 $ 表示从字符串开头匹配到结尾,避免 12a、12\n 这种局部匹配通过。
  • (?:0|[1-9]\d{0,3}) 是非捕获组,整数部分要么是单独的 0,要么是 1 到 9999,因此排除 01 和 10000。
  • (?:\.\d{1,2})? 表示小数部分整体可选;若出现小数点,后必须跟 1 到 2 位数字,排除 .5、1.、1.234。
  • 测试函数接收的是字符串,而不是数字。这样可以精确观察前导零、空格、换行和小数点等输入形态。

边界用例设计:建立正例、负例、异常输入三维矩阵 ​

设计用例时不要只按“对和错”二分,建议至少覆盖三个维度。

第一维是长度边界。若规则要求 11 位手机号,就准备 10 位、11 位、12 位;若金额最大为 9999,就测 999、9999、10000。边界值的相邻值比随机值更容易暴露闭区间、开区间错误。

第二维是字符集边界。数字规则要测字母、全角数字、下划线、空格、制表符、换行;大小写不敏感规则要同时测大小写;允许分隔符的规则要测连续分隔符和首尾分隔符。

第三维是语义边界。正则只能判断字符串形态,很多业务语义需要二次验证。例如手机号号段、身份证校验位、金额上限,不能因为“长得像”就视为完全合法。

可以将用例从测试文件迁移为 JSON,形成长期维护的校验回归库:

json
[
  {
    "ruleId": "amount-basic",
    "input": "9999.99",
    "expected": true,
    "category": "boundary",
    "note": "最大金额且保留两位小数"
  },
  {
    "ruleId": "amount-basic",
    "input": "10000",
    "expected": false,
    "category": "range",
    "note": "超过最大金额"
  }
]

执行器只负责读取 JSON、选择对应正则、比较结果并输出失败详情。这样新增规则时不必复制测试模板,历史 Bug 也可以按 ruleId 持续沉淀。

回归用例库搭建:让每次修改都可审计 ​

一个可维护的用例库建议包含以下字段:ruleId 标识规则,input 保存原始字符串,expected 表示是否通过,category 标注用例类型,bugId 可关联线上问题,note 说明业务意图。

入库时机有三个:新规则开发时,补齐正常值与边界值;线上误判修复时,必须先写失败用例,再改正则;跨语言迁移时,把同一批 JSON 用例分别跑在 JavaScript、Python 等环境中,识别方言差异。

在 CI 中执行回归也很简单:Pull Request 修改正则文件或用例文件时自动运行测试;一旦出现失败,输出规则编号、输入内容、期望结果和实际结果。这样代码评审不再只盯着一串符号,而是能看到规则变化影响了哪些具体输入。

避坑总结:正则测试中最常见的 5 个问题 ​

  1. 忘记锚定导致局部匹配通过。 现象是 /\d+/ 会让 abc123 通过,因为它只要求字符串中包含数字。修正方法是使用 ^...$ 做整串匹配;在多行文本场景则要评估 m 标志,避免 $ 把换行前位置当成结尾。

  2. 把数字先转型再测,掩盖输入问题。 现象是 Number(' 12 ') 转换后看似合法,但用户实际输入包含空格;001 转成数字后也无法发现前导零。修正方法是正则测试保留原始字符串,格式通过后再进行数值范围转换。

  3. 只写正例,不写“相邻非法值”。 现象是手机号测了 11 位通过,却没测 10 位和 12 位;金额测了 9999,却没测 10000。修正方法是为每个长度或数值边界固定准备“下界、合法值、上界、越界值”。

  4. 量词与分组括号写错,规则含义完全变化。 现象是 \.\d{1,2}? 把 {1,2} 变成非贪婪量词,仍可能只匹配一位;\.\d? 则会允许 12.。修正方法是修改后立刻查看分组解析,并用 1.、1.2、1.23、1.234 做对照测试。

  5. 跨环境复用用例但忽略正则方言差异。 现象是 JavaScript 中 \d 可能受 Unicode 标志影响,Python 原始字符串和转义规则也不同。修正方法是把 JSON 用例作为共同事实来源,在各语言分别执行;涉及 Unicode 属性、命名分组、环视时单独标注兼容矩阵。

正则表达式的工程价值不在于“短”,而在于可解释、可验证、可回归。把边界条件写成断言,把线上事故沉淀成用例,正则才会从脆弱的魔法字符串,变成受保护的业务规则。

免责声明

本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。