相同输入下JUnit测试与Android(ART)运行时正则匹配结果不一致
问题根因
1. 核心逻辑bug
你复用了同一个Matcher实例先后调用matches()和find()方法,而Matcher的所有匹配方法都会修改内部的游标位置,不同平台对匹配失败后的游标重置逻辑实现不同,直接导致结果差异:
- OpenJDK(JUnit运行环境)中,
matches()匹配整个字符串失败后,游标会被移动到字符串末尾,后续调用find()没有剩余字符可匹配,直接返回false,最终返回空列表 - Android(ICU正则引擎)中,
matches()匹配失败后游标会被重置到字符串开头,后续调用find()可以正常找到符合规则的子串,所以返回非空列表
2. 平台正则引擎实现差异
JUnit运行在标准OpenJDK环境,正则使用JDK内置的java.util.regex实现;Android平台底层使用ICU正则库,两者在边缘场景的行为存在原生差异,也是导致不一致的原因之一。
代码存在的其他问题
- 你提前把输入字符串的所有空格都删除了,正则中的
\\s?完全不会匹配任何内容,属于冗余规则,直接增加了正则的解析复杂度 findGroupMatches()逻辑完全冗余:你遍历所有捕获组后用完整正则去匹配组内容,只有0号全匹配组有可能通过校验,其余捕获组都是正则的一部分,不可能匹配完整规则,无需遍历- 捕获组遍历范围错误:
Matcher.groupCount()返回的是自定义捕获组的数量,不包含0号全匹配组,0 until matcher.groupCount()的写法无法覆盖所有自定义捕获组
修复方案
// 简化正则,删除冗余的\\s?规则 private const val REGEX = "GE[a-zA-Z0-9]{2}[a-zA-Z0-9]{2}[0-9]{2}([0-9]{4}){3}[0-9]{2}" private val PATTERN: Pattern = Pattern.compile(REGEX, Pattern.MULTILINE or Pattern.CASE_INSENSITIVE) fun find(text: String): List<String> { val textFormatted = text.replace("\\s".toRegex(), "") val list = ArrayList<String>() // 匹配整个字符串,单独使用Matcher实例 val fullMatcher = PATTERN.matcher(textFormatted) if (fullMatcher.matches()) { list.add(textFormatted) return list } // 查找子串匹配,使用新的Matcher实例 val subMatcher = PATTERN.matcher(textFormatted) while (subMatcher.find()) { // 替换为while可匹配所有子串,原写法只会匹配第一个 val match = subMatcher.group(0) if (match != null) { list.add(match) } } return list }
修复后两个平台的运行结果会完全一致,如果你需要原测试用例返回空,可以调整正则规则或者输入内容。
内容的提问来源于stack exchange,提问作者Jemo Mgebrishvili
相关产品推荐
相关产品推荐

