我刚开始接触TDD,发现正则表达式是一个特殊的情况。有没有特殊的方法来对它们进行单元测试,或者可以把它们当作普通函数对待?
我刚开始接触TDD,发现正则表达式是一个特殊的情况。有没有特殊的方法来对它们进行单元测试,或者可以把它们当作普通函数对待?
像其他代码块一样,您应该始终测试您的正则表达式。它们最简单的情况下是一个接受字符串并返回布尔值或返回值数组的函数。
以下是关于设计正则表达式单元测试时需要考虑的一些建议。这些不是单元测试设计方面的硬性规定,而是一些指导思考的准则。始终权衡测试需求、失败成本和实施所需时间。(我发现“实施”测试是易于部分! :-])
需要考虑的要点:
对于返回列表的正则表达式,还要记住:
(?<name> thing1 ( thing2) )
)——这个行为可能会因为你使用的正则表达式引擎而有所不同。如果你使用了任何高级特性,比如非回溯组,请确保你完全理解该特性的工作原理,并使用上面的准则,构建可以针对每个特性进行测试的示例字符串。
根据你使用的正则表达式库实现方式,捕获组的方式也可能有所不同。Perl 5有一个“开放括号顺序”排序,C#部分实现了该排序,但命名组等情况除外。请确保在你的语言中进行测试,以确切知道它的行为。
然后,在你的其他单元测试中将其正确集成,可以将其放在自己的模块中或与包含正则表达式的模块并排放置。对于特别复杂的正则表达式,你可能需要大量的测试来验证模式和所有使用的特性是否正确。如果正则表达式占据了方法的大部分(或几乎全部)工作,我会使用上述建议构造输入来测试该函数而不是直接测试正则表达式。这样,如果以后你决定不再使用正则表达式,或者想要拆分它,你可以在不更改接口(即调用正则表达式的方法)的情况下捕获正则表达式提供的行为。
只要你真正知道正则表达式特性在你的语言中应该如何工作,就应该能够为它开发出合理的测试用例。只要确保你真正、真正、真正地理解该特性的工作原理!
只需将一堆值传递给它,检查是否获得了正确的结果(无论是匹配/不匹配还是特定替换值等)。
重要的是,如果有任何你想知道是否能正常工作的边角案例,请在单元测试中捕获它们,并在注释中解释为什么它们能正常工作。这样,想要更改正则表达式的其他人就可以检查边角情况是否仍然有效,并且如果它出现问题,它会给他们一个提示,如何修复它。
假设您的正则表达式包含在类的方法中。例如:
public bool ValidateEmailAddress( string emailAddr )
{
// Validate the email address using regular expression.
return RegExProvider.Match( this.ValidEmailRegEx, emailAddr );
}
现在您可以为这个方法编写测试。我想关键点是正则表达式是一种实现细节 - 您的测试需要测试接口,而在这种情况下接口只是验证电子邮件方法。
考虑先编写测试,只编写所需的正则表达式来通过每个测试。如果需要扩展正则表达式,请添加失败的测试。
我总是像测试其他函数一样测试它们。确保它们匹配你认为应该匹配的内容,而不匹配不应该匹配的内容。
我喜欢针对相反的正则表达式进行测试,我会对可能的测试执行两个正则表达式,并确保它们的交集为空。
R
,你可以将整个表达式包裹在一个负向先行断言中,像这样 ^(R)
。 - tomashausergrep
,则 grep -noP
的相反指令是 grep -nvP
。 - Ahmad Ismail我认为一个简单的输入输出测试就足够了。随着时间的推移和一些情况出现,你的正则表达式失败了,别忘了在修复的同时将这些情况添加到测试中。
在您选择的单元测试库中使用fixture并遵循通常的TDD方法:
这里是Spock作为测试运行器的样本fixture存根:
@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脚本运行,例如:
groovy regex-test.groovy
声明:这段代码片段摘自我几周前写的一系列博客文章