2009年12月16日水曜日

MapReduceをJUnitでTDDで作る(6-ちょっとおさらい)

いつまで続くんだろう、このシリーズ・・・
今回は、リファクタリングの予定を変更して予定の確認と振り返りをしたいと思います。

今後のロードマップです。
  • Mapper作成(リファクタリング)
  • Mapper作成(テスト追加)
  • Mapper作成(レッド→グリーン→リファクタリング)
  • Driverのテスト(スタンドアローンモードについて)
  • Driverのテスト(テスト実行)
  • まとめ
うーん、結構長いな・・・。

少しコーヒーブレーク。今までを振り返りたいと思います。

INFOというログをログファイルから抽出するプログラムをMap(/Reduce)で作成しようとしています。
まず、実装より先にテストを作りました。
テストの内容は、「context.write();メソッドがある引数で1度だけ呼ばれればOK」というものでした。
次に実際のMapperクラスにコーディングしました。(リテラル値のハードコーディングですが)
そうしてようやくテストがOKになりました。

では、そもそも何でこのテストになったのでしょうか?

Map/Reduceを作成する上でネックとなるのは、「デバッグがしにくい」という点が挙げられます。
  • データが大量であるため、デバッグログを吐いてもどれがどのログかわかりづらい
  • 分散環境で実行するので、デバッグログを吐いてもどれがどのログかわかりづらい
  • HadoopをWindows環境で動かしにくい
そこで、mockitoを用いてモックオブジェクトを使って、Hadoopを動かさなくてもJUnit上で模擬的にMap/ReduceのロジックをUnitテストしましょう、というのが今回ここまでのお話。

ちょっと反省として、
「"INFO"というログを抽出する」という仕様と、今回のテスト仕様がどう繋がるのかわかりづらいかな、というのがあります。

まず、テストデータとして
String[] lines = {
"2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu"
};
を用意しています。これには"INFO"が含まれているので、これをMapperのmap処理に渡せば抽出対象としてcontext.writeを呼び出してくれるはずです。

ということで、
verify(context, new Times(1)).write(new Text(lines[0]), new LongWritable(1));
をテストコードとして記述しました。

Mapperの入力はKEY=LongWritable, VALUE=Text、出力はKey=LongWritable, VALUE=TextKey=Text, VALUE=LongWritableとしました。出力のKeyには行番号が連番で振られます。出力のVALUEはログの実際の抽出対象となった行の文字列です。出力のKeyはログの実際の抽出対象となった行の文字列です。出力のVALUEは1です。
この辺りの仕様をどうするかは、設計者に委ねられています。UnitTestはあくまで「プログラム仕様を満たしているかどうかをテストする」もので、業務仕様を満たしているかのテストではないのです。そこは、結合テストを実施して出力結果を検討し、業務仕様を満たしている結果になっているか確認する必要があります。(その前にUnitTestのレビューを行い、業務仕様に沿ったプログラム仕様でUnitTestが作られているかを確認するのがよいでしょう)

閑話休題。
次のリファクタリングでは、グリーンのままでどのようにリファクタリングをやっていくか、を説明したいと思います。明日時間があれば・・・。

2 件のコメント:

  1. あかん、今日書くのは無理だわ・・・
    明日以降に時間が取れれば・・・

    返信削除
  2. お詫び:
    keyに行番号では、複数datanodeでの実行時に重複が発生してしまう事に気がつきました。お詫びして訂正いたします。

    返信削除