你如何对正则表达式进行单元测试?

时间:2009-01-28 17:42:00

标签: regex unit-testing tdd

我是TDD的新手,我发现RegExp非常特殊。是否有任何特殊的单元测试方法,或者我可以将它们视为常规功能?

10 个答案:

答案 0 :(得分:83)

你应该总是测试你的regexen,就像任何其他代码块一样。它们是一个最简单的函数,它接受一个字符串并返回一个bool,或者返回一个值数组。

以下是关于为regexen设计单元测试时要考虑什么的一些建议。这些并不是单元测试设计的硬性和快速处方,而是一些塑造您思路的指导方针。与往常一样,将测试需求与故障成本进行权衡,并与实施所有测试所需的时间进行权衡。 (我发现'实施'测试很容易!: - ])

要考虑的要点:

  • 将每个组(括号)视为大括号。
  • 想想每一个|作为一个条件。确保测试每个分支。
  • 将每个修饰符(*,+,?)视为不同的路径。
  • (注意上面的内容:记住*,+,?和*?,+?和??之间的区别。)
  • 对于\ d,\ s,\ w和他们的否定,尝试在每个范围内给出几个。
  • 对于*和+,您需要测试每个的“无值”,“其中一个”和“一个或多个”。
  • 对于重要的“控制”字符(例如,您正在查找的正则表达式中的字符串)进行测试,看看如果它们出现在错误的位置会发生什么。这可能会让你感到惊讶。
  • 如果您有真实世界的数据,请尽可能多地使用它。
  • 如果不这样做,请确保测试应该有效的简单和复杂表单。
  • 确保在插入时测试正则表达式控制字符的作用。
  • 确保验证空字符串是否已正确接受/拒绝。
  • 确保验证是否正确接受或拒绝了每种不同类型的空格字符的字符串。
  • 确保正确处理不区分大小写(i标志)。在文本解析中(除了空格之外),这比其他任何东西都要多得多。
  • 如果你有x,m或s选项,请确保你明白他们做了什么并测试它(这里的行为可能不同)

对于返回列表的正则表达式,还要记住:

  • 验证您所期望的数据是否以正确的顺序返回到正确的字段中。
  • 确认轻微修改不会返回良好数据。
  • 验证混合匿名组和命名组是否正确解析(例如,(?<name> thing1 ( thing2) )) - 根据您正在使用的正则表达式引擎,此行为可能会有所不同。
  • 再一次,进行大量的现实世界试验。

如果您使用任何高级功能,例如非回溯组,请确保您完全了解该功能的工作原理,并使用上述指南,构建适用于每个功能的示例字符串。

根据您的正则表达式库实现,捕获组的方式也可能不同。 Perl 5有一个'open paren order'排序,C#部分除了命名组等等。确保尝试你的味道,确切地知道它的作用。

然后,将它们与您的其他单元测试集成在一起,可以放在自己的模块中,也可以放在包含正则表达式的模块旁边。对于特别讨厌的regexen,您可能会发现需要大量的测试来验证您使用的模式和所有功能是否正确。如果正则表达式构成了该方法正在进行的大量(或几乎所有)工作,我将使用上面的建议来设置输入以测试该函数而不是直接使用正则表达式。这样,如果以后你决定正则表达式不是要走的路,或者你想要分解,你可以捕获正则表达式提供的行为而不改变接口 - 即调用正则表达式的方法。

只要您真正了解正则表达式功能如何适用于您的正则表达式,您应该能够为它开发体面的测试用例。只要确保你真的,真的,真的了解这个功能是如何运作的!

答案 1 :(得分:11)

只需抛出一堆值,检查您是否得到了正确的结果(无论是匹配/不匹配还是特定的替换值等)。

重要的是,如果您有任何不满意的角落情况他们是否会工作,请在单元测试中捕获它们并在评论中解释为什么他们的工作。这样,想要更改正则表达式的其他人将能够检查角落案例是否仍然有效,并且它会给出一个提示,告诉他们如果它破坏了如何修复它。

答案 2 :(得分:9)

据推测,您的正则表达式包含在类的方法中。例如:

public bool ValidateEmailAddress( string emailAddr )
{
    // Validate the email address using regular expression.
    return RegExProvider.Match( this.ValidEmailRegEx, emailAddr );
}

您现在可以为此方法编写测试。我想关键是正则表达式是一个实现细节 - 您的测试需要测试接口,在这种情况下只是验证电子邮件方法。

答案 3 :(得分:3)

我会创建一组具有预期输出值的输入值,就像每个其他测试用例一样。

另外,我可以彻底推荐免费的Regex工具Expresso。 它是一个出色的正则表达式编辑器/调试器,它在过去让我度过了几天的痛苦。

答案 4 :(得分:2)

我总是像其他任何功能一样测试它们。确保它们符合您认为应该匹配的内容,并且它们与不应该匹配的内容不匹配。

答案 5 :(得分:2)

我无法相信没有人发布这个神奇的工具:

refiddle.com

它可以让你测试你的正则表达式。你定义了一些包含它应该匹配的字符串的文本,以及它不应该匹配的字符串,如果它全部是绿色的,你就是好的。例如,这是我用来匹配slu :: http://refiddle.com/by/callum-locke/slug-matcher

的一个

答案 6 :(得分:1)

我认为简单的输入输出测试就足够了。随着时间的推移和某些情况下你的正则表达式失败,不要忘记在修复时将这些情况添加到测试中。

答案 7 :(得分:1)

我喜欢针对相反的正则表达式测试正则表达式,我将针对可能的测试执行两者并确保交集为空。

答案 8 :(得分:1)

首先考虑编写测试,并且只编写通过每个测试所需的正则表达式。如果您需要扩展正则表达式,请通过添加失败测试来完成。

答案 9 :(得分:0)

在所选的单元测试库中使用固定装置,并遵循通常的TDD方法:

  • 检查:测试为绿色
  • 通过添加针对下一个“功能”的测试来破坏测试
  • 通过调整正则表达式使其绿色(不破坏现有测试)
  • 重构正则表达式以提高可读性(例如,命名组,字符类而不是字符范围等)

以下是供spock作为测试跑步者的样品夹具存根:

@Grab('org.spockframework:spock-core:1.3-groovy-2.5')
@GrabExclude('org.codehaus.groovy:groovy-nio')
@GrabExclude('org.codehaus.groovy:groovy-macro')
@GrabExclude('org.codehaus.groovy:groovy-sql')
@GrabExclude('org.codehaus.groovy:groovy-xml')

import spock.lang.Unroll

class RegexSpec extends spock.lang.Specification {
  String REGEX = /[-+]?\d+(\.\d+)?([eE][-+]?\d+)?/

  @Unroll
  def 'matching example #example for case "#description" should yield #isMatchExpected'(String description, String example, Boolean isMatchExpected) {
    expect:
    isMatchExpected == (example ==~ REGEX)

    where:
    description                                  | example        || isMatchExpected
    "empty string"                               | ""             || false
    "single non-digit"                           | "a"            || false
    "single digit"                               | "1"            || true
    "integer"                                    | "123"          || true
    "integer, negative sign"                     | "-123"         || true
    "integer, positive sign"                     | "+123"         || true
    "float"                                      | "123.12"       || true
    "float with exponent extension but no value" | "123.12e"      || false
    "float with exponent"                        | "123.12e12"    || true
    "float with uppercase exponent"              | "123.12E12"    || true
    "float with non-integer exponent"            | "123.12e12.12" || false
    "float with exponent, positive sign"         | "123.12e+12"   || true
    "float with exponent, negative sign"         | "123.12e-12"   || true
  }
}

它可以像一个独立的常规脚本一样运行

groovy regex-test.groovy

免责声明:该摘录摘自我几周前写的一系列博客文章