web-dev-qa-db-ja.com

正規表現をどのように単体テストしますか?

私はTDDを初めて使用しますが、RegExpは非常に特殊なケースです。それらをユニットテストする特別な方法はありますか、それとも通常の関数として扱うことができますか?

53
Jader Dias

他のコードのチャンクと同じように、常にregexenをテストする必要があります。これらは、文字列を受け取ってブール値を返すか、値の配列を返す最も単純な関数です。

Regexenの単体テストを設計する際に考慮すべきいくつかの提案があります。これらは、単体テスト設計の厳格で迅速な処方箋ではありませんが、思考を形作るためのいくつかのガイドラインです。いつものように、テストのニーズと失敗のコストを、それらすべてを実装するために必要な時間とバランスをとって比較検討します。 (テストの「実装」は簡単な部分だと思います!:-])

考慮すべきポイント:

  • すべてのグループ(括弧)を中括弧と考えてください。
  • すべてを考える|条件として。必ず各ブランチをテストしてください。
  • すべての修飾子(*、+、?)を異なるパスと考えてください。
  • (上記の補足:*、+、?と*?、+?、および??の違いを覚えておいてください。)
  • \ d、\ s、\ w、およびそれらの否定については、各範囲のいくつかを試してみてください。
  • *および+の場合、それぞれについて「値なし」、「いずれか」、および「1つ以上」をテストする必要があります。
  • 重要な「制御」文字(たとえば、探している正規表現の文字列)については、間違った場所に表示された場合にどうなるかをテストします。これはあなたを驚かせるかもしれません。
  • 実世界のデータがある場合は、できるだけ多くのデータを使用してください。
  • そうでない場合は、有効であるはずの単純なフォームと複雑なフォームの両方をテストしてください。
  • 挿入時に正規表現制御文字がどのように機能するかを必ずテストしてください。
  • 空の文字列が適切に受け入れ/拒否されていることを確認してください。
  • 異なる種類のスペース文字のそれぞれの文字列が適切に受け入れられるか拒否されることを確認してください。
  • 大文字と小文字を区別しない処理が適切に行われていることを確認してください(iフラグ)。これは、テキスト解析(スペース以外)で他のほとんどのことよりも何度も私を噛みました。
  • X、m、またはsオプションがある場合は、それらの機能を理解してテストしてください(ここでの動作は異なる場合があります)

リストを返す正規表現については、次のことも覚えておいてください。

  • 期待するデータが正しい順序で正しいフィールドに返されることを確認します。
  • わずかな変更で適切なデータが返されないことを確認します。
  • 混合された匿名グループと名前付きグループが正しく解析されることを確認します(例:(?<name> thing1 ( thing2) ))-この動作は、使用している正規表現エンジンによって異なる場合があります。
  • 繰り返しになりますが、実際に試してみてください。

非バックトラッキンググループなどの高度な機能を使用する場合は、その機能がどのように機能するかを完全に理解し、上記のガイドラインを使用して、それぞれに対して機能するサンプル文字列を作成してください。

正規表現ライブラリの実装によっては、グループのキャプチャ方法も異なる場合があります。 Perl 5には「オープンパレンオーダー」の順序があり、C#には名前付きグループなどを除いて部分的にそれがあります。フレーバーが何をするのかを正確に知るために、フレーバーを試してみてください。

次に、それらを他の単体テストと統合します。独自のモジュールで、または正規表現を含むモジュールと一緒に統合します。特に厄介なregexenの場合、使用するパターンとすべての機能が正しいことを確認するために、多くのテストが必要になる場合があります。正規表現がメソッドが実行している作業の大部分(またはほぼすべて)を構成している場合は、上記のアドバイスを使用して、正規表現ではなく、その関数をテストするための入力を作成します。そうすれば、後で正規表現がうまくいかないと判断した場合、または正規表現を分割したい場合は、インターフェイス(つまり、正規表現を呼び出すメソッド)を変更せずに、正規表現が提供する動作をキャプチャできます。

正規表現機能が正規表現のフレーバーでどのように機能するかを本当に知っている限り、それに対する適切なテストケースを開発できるはずです。機能がどのように機能するかを本当に、本当に、本当に理解していることを確認してください!

86
Robert P

そこにたくさんの値を投げて、正しい結果が得られることを確認します(一致/不一致、または特定の置換値など)。

重要なのは、あなたが不思議うまくいくかどうかというコーナーケースがある場合は、それらを単体テストでキャプチャし、コメントで説明してくださいなぜうまくいくか。そうすれば、正規表現を変更したい他の誰かが、コーナーケースがまだ機能していることを確認でき、壊れた場合の修正方法についてのヒントが得られます。

11
Jon Skeet

おそらく、正規表現はクラスのメソッド内に含まれています。例えば:

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

これで、このメソッドのテストを作成できます。重要なのは、正規表現が実装の詳細であるということです。テストではインターフェイスをテストする必要があります。この場合は、電子メールの検証メソッドにすぎません。

9
ng5000

他のすべてのテストケースと同じように、期待される出力値を使用して入力値のセットを作成します。

また、無料の正規表現ツール Expresso を徹底的にお勧めします。これは、過去の苦痛の日々を救ってくれた素晴らしい正規表現エディター/デバッガーです。

3
Andrew Rollings

この素晴らしいツールを誰も投稿していないなんて信じられません。

refiddle.com

正規表現をテストできます。一致する必要のある文字列と一致しない文字列を含むテキストを定義します。すべて緑色の場合は問題ありません。たとえば、ナメクジと一致させるために作成したものは次のとおりです。 http://refiddle.com/by/callum-locke/slug-matcher

2
callum

他の機能と同じように、常にテストします。一致する必要があると思われるものと一致し、一致してはならないものと一致しないことを確認してください。

2
Bill the Lizard

最初にテストを作成し、各テストに合格するために必要な量の正規表現のみを作成することを検討してください。正規表現を拡張する必要がある場合は、失敗したテストを追加してください。

1
Andrew Grimm

簡単な入出力テストで十分だと思います。時間が経ち、正規表現が失敗する場合が発生する場合は、修正中にこれらのケースもテストに追加することを忘れないでください。

1
DaSteph

反対の正規表現に対して正規表現をテストするのが好きです。可能なテストに対して両方を実行し、交差点が空であることを確認します。

1
mandel

選択した単体テストライブラリのフィクスチャを使用し、通常のTDDアプローチに従います。

  • チェック:テストは緑色です
  • 次の「機能」のテストを追加して、テストを中断します
  • 正規表現を調整して(既存のテストを壊さずに)緑にします
  • 読みやすくするために正規表現をリファクタリングします(例:名前付きグループ、文字範囲の代わりに文字クラスなど)

テストランナーとしてのスポックのサンプルフィクスチャスタブは次のとおりです。

@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

免責事項:スニペットは、数週間前に書いた一連のブログ投稿から抜粋したものです。

0
JFK