Hadoop學習筆記之三:用MRUnit做單元測試
阿新 • • 發佈:2019-01-23
引言
借年底盛宴品鑑之風,繼續抒我Hadoop之情,本篇文章介紹如何對Hadoop的MapReduce進行單元測試。MapReduce的開發週期差不多是這樣:編寫mapper和reducer、編譯、打包、提交作業和結果檢索等,這個過程比較繁瑣,一旦提交到分散式環境出了問題要定位除錯,重複這樣的過程實在無趣,因此先對MapReduce做單元測試,消除明顯的程式碼bug尤為必要。
MRUnit簡介
MRUnit是一款由Couldera公司開發的專門針對Hadoop中編寫MapReduce單元測試的框架。可以用MapDriver單獨測試Map,用ReduceDriver單獨測試Reduce,用MapReduceDriver測試MapReduce作業。
實戰
我們將利用MRUnit對本系列上篇文章MapReduce基本程式設計中的字數統計功能進行單元測試。
加入MRUnit依賴
<dependency>
<groupId>com.cloudera.hadoop</groupId>
<artifactId>hadoop-mrunit</artifactId>
<version>0.20.2-320</version>
<scope>test</scope>
</dependency>
單獨測試Map
public class WordCountMapperTest {
private Mapper mapper;
private MapDriver driver;
@Before
public void init(){
mapper = new WordCountMapper();
driver = new MapDriver(mapper);
}
@Test
public void test() throws IOException{
String line = "Taobao is a great website";
driver.withInput(null,new Text(line))
.withOutput(new Text("Taobao"),new IntWritable(1))
.withOutput(new Text("is"), new IntWritable(1))
.withOutput(new Text("a"), new IntWritable(1))
.withOutput(new Text("great"), new IntWritable(1))
.withOutput(new Text("website"), new IntWritable(1))
.runTest();
}
}
上面的例子通過MapDriver的withInput和withOutput組織map函式的輸入鍵值和期待的輸出鍵值,通過runTest方法執行作業,測試 Map函式。
測試執行通過。
單獨測試Reduce
public class WordCountReducerTest {
private Reducer reducer;
private ReduceDriver driver;
@Before
public void init(){
reducer = new WordCountReducer();
driver = new ReduceDriver(reducer);
}
@Test
public void test() throws IOException{
String key = "taobao";
List values = new ArrayList();
values.add(new IntWritable(2));
values.add(new IntWritable(3));
driver.withInput(new Text("taobao"), values)
.withOutput(new Text("taobao"), new IntWritable(5))
.runTest();
}
}
上面的例子的測試Map函式的寫法類似,測試 reduce函式,
因為reduce函式實現相加功能,因此我們假設輸入為<taobao,[2,3]>,
則期待結果應該為<taobao,5>.測試執行通過。
測試MapReduce
public class WordCountTest {
private Mapper mapper;
private Reducer reducer;
private MapReduceDriver driver;
@Before
public void init(){
mapper = new WordCountMapper();
reducer = new WordCountReducer();
driver = new MapReduceDriver(mapper,reducer);
}
@Test
public void test() throws RuntimeException, IOException{
String line = "Taobao is a great website, is it not?";
driver.withInput("",new Text(line))
.withOutput(new Text("Taobao"),new IntWritable(1))
.withOutput(new Text("a"),new IntWritable(1))
.withOutput(new Text("great"),new IntWritable(1))
.withOutput(new Text("is"),new IntWritable(2))
.withOutput(new Text("it"),new IntWritable(1))
.withOutput(new Text("not"),new IntWritable(1))
.withOutput(new Text("website"),new IntWritable(1))
.runTest();
}
}
這次我們測試MapReduce的作業,通過MapReduceDriver的withInput構造map函式的輸入鍵值,通過withOutput構造reduce函式的輸出鍵值。來測試這個字數統計功能,這次執行測試時丟擲了異常,測試沒有通過但沒有詳細junit異常資訊,在控制檯顯示
2010-11-5 11:14:08 org.apache.hadoop.mrunit.TestDriver lookupExpectedValue嚴重:Received unexpected output (not?, 1)
2010-11-5 11:14:08 org.apache.hadoop.mrunit.TestDriver lookupExpectedValue嚴重: Received unexpected output (website,, 1)
2010-11-5 11:14:08 org.apache.hadoop.mrunit.TestDriver validate嚴重:Missing expected output (not, 1) at position 5
2010-11-5 11:14:08 org.apache.hadoop.mrunit.TestDriver validate嚴重:Missing expected output (website, 1) at position 6
看樣子是那裡出了問題,不過看控制檯日誌不是很直觀,因此我們修改測試程式碼,不呼叫runTest方法,而是呼叫run方法獲取輸出結果,再跟期待結果相比較,mrunit提供了org.apache.hadoop.mrunit.testutil.ExtendedAssert.assertListEquals輔助類來斷言輸出結果。
重構後的測試程式碼
@Test
public void test() throws RuntimeException, IOException{
String line = "Taobao is a great website, is it not?";
List<Pair> out = null;
out = driver.withInput("",new Text(line)).run();
List<Pair> expected = new ArrayList<Pair>();
expected.add(new Pair(new Text("Taobao"),new IntWritable(1)));
expected.add(new Pair(new Text("a"),new IntWritable(1)));
expected.add(new Pair(new Text("great"),new IntWritable(1)));
expected.add(new Pair(new Text("is"),new IntWritable(2)));
expected.add(new Pair(new Text("it"),new IntWritable(1)));
expected.add(new Pair(new Text("not"),new IntWritable(1)));
expected.add(new Pair(new Text("website"),new IntWritable(1)));
assertListEquals(expected, out);
}
再次執行,測試不通過,但有了明確的斷言資訊,
java.lang.AssertionError: Expected element (not, 1) at index 5 != actual element (not?, 1)
斷言顯示實際輸出的結果為"not?"不是我們期待的"not",為什麼?檢查Map函式,發現程式以空格為分隔符未考慮到標點符號的情況,哈哈,發現一個bug,趕緊修改吧。這個問題也反映了單元測試的重要性,想想看,如果是一個更加複雜的運算,不做單元測試直接放到分散式叢集中去執行,當結果不符時就沒這麼容易定位出問題了。
小結
用MRUnit做單元測試可以歸納為以下幾點:用MapDriver單獨測試Map,用ReduceDriver單獨測試Reduce,用MapReduceDriver測試MapReduce作業;不建議呼叫runTest方法,建議呼叫run方法獲取輸出結果,再跟期待結果相比較;對結果的斷言可以藉助org.apache.hadoop.mrunit.testutil.ExtendedAssert.assertListEquals。
如果你能堅持看到這裡,我非常高興,但我打賭,你肯定對前面大片的程式碼匆匆一瞥而過,這也正常,不是每個人都對測試實戰的程式碼感興趣(或在具體需要時才感興趣),為了感謝你的關注,我再分享一個小祕密:本篇講的不僅僅是如何對MapReduce做單元測試,通過本篇測試程式碼的閱讀,你可以更加深刻的理解MapReduce的原理(通過測試程式碼的輸入和預期結果,你可以更加清楚地知道map、reduce究竟輸入、輸出了什麼,對結果的排序在何處進行等細節)。
單元測試很必要,可以較早較容易地發現定位問題,但只有單元測試是不夠的,我們需要對MapReduce進行整合測試,在執行整合測試之前,需要掌握如何將MapReduce 作業在hadoop叢集中執行起來,本系列後面的文章將介紹這部分內容。