辞書を片手に~PatternResponderの作成(2) | Pythonでなんか作ってみる

辞書を片手に~PatternResponderの作成(2)

ToDoリスト

・パターン辞書から正規表現のパターンオブジェクトのグループを作る


まずは、パターンオブジェクトのグループを作ってみる。

グループとは何だろう?配列か?それともTreeMapがいいのか?

TreeMapが必要だと感じるのは、この先に控えている、「パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める」を意識しているからだと思われる。


そんな先のことを意識しないとテストが考えられないというのは、着手順番を間違えた証拠かもしれない。

前言撤回して、この「パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める」から手をつけようかとも思ったが、それはそれで面倒そうだったので、とりあえず配列で考えることにする。


配列と決まった場合、このToDoをこなすには必要なのは、どうやってパターンオブジェクトの配列が出来たかをテストするのかだ。PatternクラスはAssertEqualsに対してどうやって振舞うのだろうか?


public void testAsserEqualsForPattern() {
assertEquals(Pattern.compile(".*"),Pattern.compile(".*"));
}

Red。どうやらequalsメソッドに対しては同じパターンでも別物として扱われるようだ。


pattern ()
このパターンのコンパイル元の正規表現を返します。


というメソッドがあるらしいので、それを経由してみる。


public void testAsserEqualsForPattern() {
assertEquals(Pattern.compile(".*").pattern(),Pattern.compile(".*").pattern());
}

Green。どうにか、これでうまくいきそうだ。


辞書ファイルに関しては、とりあえずRandom辞書で使ったものと同じものを再利用して、それから文字列の配列ではなく、正規表現のパターンの配列を取得するメソッドをテストする。

上のテスト結果と以前の文字列の配列をテストするassertEquals()を元にパターンオブジェクトの配列をテストするassertEqualsを作る。


public void testAsserEqualsForPattern() {
Pattern[] p1 = {Pattern.compile("1"),Pattern.compile("2")};
Pattern[] p2 = {Pattern.compile("1"),Pattern.compile("2")};
Pattern[] p3 = {Pattern.compile("1"),Pattern.compile("3")};

assertEquals(p1,p2);
assertEquals(p1,p3);
}

public void assertEquals(Pattern[] expected, Pattern[] actual) {
for (int i=0;i<Math.min(actual.length,expected.length);i++) {
assertEquals(expected[i].pattern(),actual[i].pattern());
}
assertEquals(expected.length,actual.length);
}

テストを実行する。p1とp2のテストで失敗する。予想どおりなのでtestAsserEqualsForPattern()は削除して早速テストを追加する。


public void testLoadDictionary() throws FileNotFoundException {
assertEquals(new Pattern[] { Pattern.compile("今日はさむいね"),
Pattern.compile("チョコたべたい"), Pattern.compile("きのう10円ひろった") },
PatternResponder
.loadDictionary(new FileReader("dics/first.dat")));
assertEquals(new Pattern[] { Pattern.compile("今日はさむいね"),
Pattern.compile("チョコたべたい"), Pattern.compile("きのう10円ひろった"),
Pattern.compile("それからそれから?") },
PatternResponder
.loadDictionary(new FileReader("dics/add.dat")));
}

これは、RandomResponderのloadDictionary()のテストをコピペして改造したものだ。

まだ、PatternResponderが存在しないのでテストは実行できない。QuickFixを何回か実行して、PatternResponderを作る。


public class PatternResponder {

public static Pattern[] loadDictionary(FileReader reader) {
// TODO Auto-generated method stub
return null;
}

}

テスト実行。当然Red。

中身を実装する。

しかし、テストをRandomResponderのテストから流用したのだが、ここで思い出してもらいたい。このテストは最初からこの形だったのではなく、TDDを進めてこの形に落ち着いたのだ。

つまり、このテストでは開発を駆動するには最終形過ぎるのだ。

では、どうするのか?

一旦、このテストを削除してやり直すというのもいい手なのかもしれないが、ここでよく考えてみる。

このテストが最終形をちょっと手直ししたということは、このテストに対応した実装をちょっと手直しすると本実装になるのではないか?

つまり、PatternResponderのloadDictionary()のテストはRandomResponderのloadDictionary()のテストに毛を生やしたものを使ったのだから、loadDictionary()の実装も毛を生やせばいいのではないかと。


テストの場合は毛を生やす方法は、コピーして修正だったが、実装の場合は委譲を用いる。


まずは、毛を生やす前ので実装する。


public static Pattern[] loadDictionary(FileReader reader) {
return RandomResponder.loadDictionary(reader);
}

コンパイルエラーである。Stringの配列をPatternの配列に変換できない。

まずは1個ずつ変換するしか無いだろう。


public static Pattern[] loadDictionary(FileReader reader) {
String[] patternStrings = RandomResponder.loadDictionary(reader);
Pattern[] patterns = new Pattern[patternStrings.length];

for (int index=0;index < patterns.length; index++) {
patterns[index] = Pattern.compile(patternStrings[index]);
}
return patterns;
}

テスト実行。Green!!

なんか簡単に出来てしまったが、PatternResponderがRandomResponderのメソッドを利用するのが依存関係的に気に入らない。ToDoに追加しておく。


ToDoリスト

・正規表現を使ってパターンに反応するResponder(以下の仕様を満たす)

・パターン辞書の先頭行からパターンマッチを行い、マッチした行の応答例をもとに応答メッセージを作る

・1つのパターンに対して応答例は「|」で区切って複数設定でき、いずれかがランダムに選択される

・マッチするパターンがなかったときは、ランダム辞書からランダムに選択した応答を返す

・応答例の中に「%match」という文字列があれば、パターンにマッチした文字列と置き換えられる

・パターン辞書から正規表現のパターンオブジェクトのグループを作る

・パターン辞書から応答例のグループを作る

・パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める

・PatternResponderがRandomResponderの機能を利用している(メソッドの上位クラスへの移動に関するリファクタリングが出来そうである)