ここまでのソースコードです。
package jp.co.littel.hadoop;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.internal.verification.Times;
/**
*
* @author littel
*/
public class TestLogAnalysisMapper {
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
}
/**
* 抽出するパターン
* @throws Exception 例外発生時
*/
@Test
public void testMapExtract() throws Exception {
Mapper.Context context = mock(Mapper.Context.class);
String[] lines = {
"2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu"
};
verify(context, new Times(1)).write(new LongWritable(1), new Text(lines[0]));
verify(context, new Times(1)).write(new Text(lines[0]), new LongWritable(1));
}
}
前回のエラーの内容を振り返ってみると、
Wanted but not invoked
とありました。
「context.writeメソッドが1, とlines[0]で呼ばれるってテストには書いてあるんだけど、呼ばれなかったよ?」と言っています。
ということは、テストをグリーンにするには、context.writeメソッドを呼び出す仕組みを作れば良さそうです。
つまり、Mapper.mapメソッドを呼び出さねば!
Mapper.map(key, value, context)メソッドの呼び出しは次のように行います。
LogAnalysisMapper mapper = new LogAnalysisMapper();
mapper.map(null, new Text(lines[0]), context);
早速、ソースコードのtestMapExtract()メソッドのverifyメソッドの手前に追加しましょう。
もちろんコンパイルエラーになりますね。LogAnalysisMapperクラスなんて存在しないからです。
Eclipseのクイックフィックス機能でクラスを追加しましょう。[Ctrl]+[1]キーを押下して下さい。
「クラス'LogAnalysisMapper'を作成します」をクリックして下さい。
「新規」ダイアログが表示されるので、
ソースフォルダ:LogAnalysisHadoop/src
スーパークラス:org.apache.hadoop.mapreduce.Mapper
継承された抽象メソッド:チェックあり
で「完了」ボタンを押下します。
LogAnalysisMapper.javaファイルが生成されました。が、まだコンパイルエラーはなくなっていません。
LogAnalysisMapper.javaをEclipseのJavaエディタで開き、右クリックして、「ソース」→「メソッドのオーバーライド/実装」をクリックしましょう。
mapメソッドにチェックを入れて、「OK」ボタンをクリックします。
クラス内にmapメソッドが追加されます。
コンパイルエラーが解消されたので、実行してみます。
またレッド(失敗)になりました。前回とは少し異なるようです。
org.mockito.exceptions.verification.junit.ArgumentsAreDifferent:
Argument(s) are different! Wanted:
context.write(
1,
2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu
);
context.write(
2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu1,
);
at jp.co.littel.hadoop.TestLogAnalysisMapper.testMapExtract(TestLogAnalysisMapper.java:50)
Caused by: org.mockito.exceptions.cause.ActualArgumentsAreDifferent:
Actual invocation has different arguments:
context.write(
null,
2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu
);
今度は、「呼ばれたけど引数が違う!」というエラーのようです。
さきほど作成したメソッドの中でスーパークラスのmapメソッドをそのまま呼び出しているため、キーのnullがそのまま引き渡されてしまっているようです。
では、このレッドをグリーンに変えましょう。どうすればいいでしょうか?
「mapメソッドに、仕様を満たすようにINFOのログを抜き出す処理を記述すればいいんだよ」
残念ながら難しく考えすぎです。
正解は
「mapメソッド内で
TDDでは、とにかくシンプルに作成することを第一に考えます。
context.writeの呼び出し引数にLongWritable(1)とText("<省略>")が渡されればいいのですから、それをそのまま呼び出せばいいのです。
「おいおい、それじゃ別のテストケースに対応できないじゃん?」
その通り。それはテストがグリーンになった後でリファクタリングしましょう。
では、メソッドにcotext.write();を記述して、テストを実行してみます。
おめでとうございます!見事にグリーンになりました!!!
LogAnalysisMapper.javaのソースは次のようになっています。
package jp.co.littel.hadoop;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
/**
*
* @author yoshitsugu
*/
public class LogAnalysisMapper extends Mapper {
/* (non-Javadoc)
* @see org.apache.hadoop.mapreduce.Mapper#map(java.lang.Object, java.lang.Object, org.apache.hadoop.mapreduce.Mapper.Context)
*/
@Override
protected void map(Object key, Object value, Context context)
throws IOException, InterruptedException {
context.write(new LongWritable(1), new Text("2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu"));
context.write(new Text("2009-12-14 00:00:26,340 INFO hogehoge @ abcdefg hijklmn opqrstu"), new LongWritable(1));
}
}
次はリファクタリングしてこのソースをもっと洗練します。
お詫び:
返信削除context.write()の引数を入れ替えないとdatanodeが複数になったときに結果がおかしくなってしまうことに気づきました。謹んで訂正いたします。