HBaseをデータストアにしたMapReduceをMRUnitでテストする時の注意

MapReduceのテストをゼロから書こうと思うと超絶しんどいですが、それをサポートしてくれるライブラリとしてMRUnitというものがあります。


使い方としては

@Test
public void テスト() throws Exception{
  MapDriver<InKey, InValue, OutKey, OutValue> mapDriver = new MapDriver<InKey, InValue, OutKey, OutValue>(new HogeMapper());
  mapDriver
    .withInput(inputKey, inputValue)
    .withOutput(outputKey, outputValue)
    .runTest();
}

こんな感じで、withInputに指定されたkeyとvalueをMapperに渡して処理をさせ、その結果がwithOutputに指定されたkeyとvalueに等しいかどうかをrunTest()内で検証してくれます。

runTest()内で走る検証処理はwithOutput()で指定されたオブジェクトと実際にMapperが出力したオブジェクトをequals()メソッドを使って比較するというものなのですが*1、データストアがHBaseだとここに問題が出てきます。
と言うのも、HBaseをMapReduceのデータストアにする場合、outputの値の型はPutとかDeleteになると思いますが、これらのオブジェクトはequals()メソッドをオーバーライドしていないため、想定する比較*2を行なってくれません。

回避策

1. Putを継承した独自クラスでequals()をオーバーライドする

内容の等価性で比較可能なPutExtみたいなクラスを作る方法。
下記のTestDriver#validate()をオーバーライドする方法よりかなりシンプルになりますが、プロダクションコードに影響を与えてしまうのがイケてない。
あと、equals()をオーバーライドした場合hashCode()メソッドもオーバーライドするべきというお行儀がありますが、Putの内容の本体であるKeyValue型のequlas()メソッドは内容の完全一致という方針で実装されていないため、PutのhashCode算出にこれのhashCodeを利用することができないとか面倒な話があります。

2. TestDriverクラスのvalidate()メソッドをオーバーライドする

上記のMapDriverや、これのReduce版であるReduceDriver等の**Driverクラスには、基底クラスとしてTestDriverが存在しています。
runTest()メソッドの中で実行されている出力の検証処理の本体は、このTestDriverクラスのvalidate()メソッドなので、こいつをオーバーライドしてやれば検証処理を独自のものに変更できます。
メリットとしてはプロダクションコードに影響を与えないこと。デメリットとしては、validate()メソッド自体がTestDriverクラスのプライベートメソッドに依存していたりするので、結構ごっそりコードをコピペしてきて泥臭く改造しないといけない所。


どちらもそれなりにめんどいですが、個人的にはvalidate()メソッドのオーバーライドをオススメします*3

ちなみに

HBaseのissueにも似たような話は上がってたらしいんですが、上記のequals-hashCode周りの契約だとかをちゃんとしようと思うと既存実装に対する影響が大きいので直さないことにした、みたいなこと言ってました。*4

*1:まぁ、当たり前ですよね

*2:更新オブジェクトの内容が等しいかどうか

*3:プロダクションコード触りたくないですよね

*4:英語力低いの自信ないです信じないでください