qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

          Hadoop:用MRUnit做單元測(cè)試

           引言
            借年底盛宴品鑒之風(fēng),繼續(xù)抒我Hadoop之情,本篇文章介紹如何對(duì)Hadoop的MapReduce進(jìn)行單元測(cè)試。MapReduce的開發(fā)周期差不多是這樣:編寫mapper和reducer、編譯、打包、提交作業(yè)和結(jié)果檢索等,這個(gè)過程比較繁瑣,一旦提交到分布式環(huán)境出了問題要定位調(diào)試,重復(fù)這樣的過程實(shí)在無趣,因此先對(duì)MapReduce做單元測(cè)試,消除明顯的代碼bug尤為必要。
            MRUnit簡(jiǎn)介
            MRUnit是一款由Couldera公司開發(fā)的專門針對(duì)Hadoop中編寫MapReduce單元測(cè)試的框架??梢杂肕apDriver單獨(dú)測(cè)試Map,用ReduceDriver單獨(dú)測(cè)試Reduce,用MapReduceDriver測(cè)試MapReduce作業(yè)。
            實(shí)戰(zhàn)
            我們將利用MRUnit對(duì)本系列上篇文章MapReduce基本編程中的字?jǐn)?shù)統(tǒng)計(jì)功能進(jìn)行單元測(cè)試。
            ·  加入MRUnit依賴
          <dependency>
          <groupId>com.cloudera.hadoop</groupId>
          <artifactId>hadoop-mrunit</artifactId>
          <version>0.20.2-320</version>
          <scope>test</scope>
          </dependency>
            · 單獨(dú)測(cè)試Map
          public class WordCountMapperTest {
          private Mappermapper;
          private MapDriverdriver;
          @Before
          public voidinit(){
          mapper = newWordCountMapper();
          driver = newMapDriver(mapper);
          }
          @Test
          public voidtest() throws IOException{
          String line ="Taobao is a great website";
          driver.withInput(null,newText(line))
          .withOutput(newText("Taobao"),new IntWritable(1))
          .withOutput(newText("is"), new IntWritable(1))
          .withOutput(newText("a"), new IntWritable(1))
          .withOutput(newText("great"), new IntWritable(1))
          .withOutput(newText("website"), new IntWritable(1))
          .runTest();
          }
          }
            上面的例子通過MapDriver的withInput和withOutput組織map函數(shù)的輸入鍵值和期待的輸出鍵值,通過runTest方法運(yùn)行作業(yè),測(cè)試Map函數(shù)。測(cè)試運(yùn)行通過。
            · 單獨(dú)測(cè)試Reduce
          public class WordCountReducerTest {
          private Reducerreducer;
          privateReduceDriver driver;
          @Before
          public voidinit(){
          reducer = newWordCountReducer();
          driver = newReduceDriver(reducer);
          }
          @Test
          public voidtest() throws IOException{
          String key ="taobao";
          List values =new ArrayList();
          values.add(newIntWritable(2));
          values.add(newIntWritable(3));
          driver.withInput(new Text("taobao"), values)
          .withOutput(new Text("taobao"), new IntWritable(5))
          .runTest();
          }
          }
          \ 上面的例子的測(cè)試Map函數(shù)的寫法類似,測(cè)試reduce函數(shù),
            因?yàn)閞educe函數(shù)實(shí)現(xiàn)相加功能,因此我們假設(shè)輸入為<taobao,[2,3]>,
            則期待結(jié)果應(yīng)該為<taobao,5>.測(cè)試運(yùn)行通過。
            ·  測(cè)試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();
          }
          }
            這次我們測(cè)試MapReduce的作業(yè),通過MapReduceDriver的withInput構(gòu)造map函數(shù)的輸入鍵值,通過withOutput構(gòu)造reduce函數(shù)的輸出鍵值。來測(cè)試這個(gè)字?jǐn)?shù)統(tǒng)計(jì)功能,這次運(yùn)行測(cè)試時(shí)拋出了異常,測(cè)試沒有通過但沒有詳細(xì)junit異常信息,在控制臺(tái)顯示
            2010-11-5 11:14:08org.apache.hadoop.mrunit.TestDriver lookupExpectedValue嚴(yán)重:Received unexpectedoutput (not?, 1)
            2010-11-5 11:14:08org.apache.hadoop.mrunit.TestDriver lookupExpectedValue嚴(yán)重: Received unexpectedoutput (website,, 1)
            2010-11-5 11:14:08org.apache.hadoop.mrunit.TestDriver validate嚴(yán)重:Missing expected output (not, 1) atposition 5
            2010-11-5 11:14:08org.apache.hadoop.mrunit.TestDriver validate嚴(yán)重:Missing expected output (website, 1)at position 6
            看樣子是那里出了問題,不過看控制臺(tái)日志不是很直觀,因此我們修改測(cè)試代碼,不調(diào)用runTest方法,而是調(diào)用run方法獲取輸出結(jié)果,再跟期待結(jié)果相比較,mrunit提供了org.apache.hadoop.mrunit.testutil.ExtendedAssert.assertListEquals輔助類來斷言輸出結(jié)果。
            重構(gòu)后的測(cè)試代碼
          @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);
          }
            再次運(yùn)行,測(cè)試不通過,但有了明確的斷言信息,
            java.lang.AssertionError:Expected element (not, 1) at index 5 != actual element (not?, 1)
            斷言顯示實(shí)際輸出的結(jié)果為"not?"不是我們期待的"not",為什么?檢查Map函數(shù),發(fā)現(xiàn)程序以空格為分隔符未考慮到標(biāo)點(diǎn)符號(hào)的情況,哈哈,發(fā)現(xiàn)一個(gè)bug,趕緊修改吧。這個(gè)問題也反映了單元測(cè)試的重要性,想想看,如果是一個(gè)更加復(fù)雜的運(yùn)算,不做單元測(cè)試直接放到分布式集群中去運(yùn)行,當(dāng)結(jié)果不符時(shí)就沒這么容易定位出問題了。
            小結(jié)
            用MRUnit做單元測(cè)試可以歸納為以下幾點(diǎn):用MapDriver單獨(dú)測(cè)試Map,用ReduceDriver單獨(dú)測(cè)試Reduce,用MapReduceDriver測(cè)試MapReduce作業(yè);不建議調(diào)用runTest方法,建議調(diào)用run方法獲取輸出結(jié)果,再跟期待結(jié)果相比較;對(duì)結(jié)果的斷言可以借助org.apache.hadoop.mrunit.testutil.ExtendedAssert.assertListEquals。
            如果你能堅(jiān)持看到這里,我非常高興,但我打賭,你肯定對(duì)前面大片的代碼匆匆一瞥而過,這也正常,不是每個(gè)人都對(duì)測(cè)試實(shí)戰(zhàn)的代碼感興趣(或在具體需要時(shí)才感興趣),為了感謝你的關(guān)注,我再分享一個(gè)小秘密:本篇講的不僅僅是如何對(duì)MapReduce做單元測(cè)試,通過本篇測(cè)試代碼的閱讀,你可以更加深刻的理解MapReduce的原理(通過測(cè)試代碼的輸入和預(yù)期結(jié)果,你可以更加清楚地知道m(xù)ap、reduce究竟輸入、輸出了什么,對(duì)結(jié)果的排序在何處進(jìn)行等細(xì)節(jié))。
            單元測(cè)試很必要,可以較早較容易地發(fā)現(xiàn)定位問題,但只有單元測(cè)試是不夠的,我們需要對(duì)MapReduce進(jìn)行集成測(cè)試,在運(yùn)行集成測(cè)試之前,需要掌握如何將MapReduce 作業(yè)在hadoop集群中運(yùn)行起來,本系列后面的文章將介紹這部分內(nèi)容。

          posted on 2014-01-29 10:44 順其自然EVO 閱讀(404) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          <2014年1月>
          2930311234
          567891011
          12131415161718
          19202122232425
          2627282930311
          2345678

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 松溪县| 福泉市| 兴安盟| 中牟县| 大邑县| 上虞市| 兴安县| 阆中市| 英超| 伽师县| 赞皇县| 山东省| 醴陵市| 石首市| 许昌县| 永昌县| 大方县| 蒙阴县| 景德镇市| 苏州市| 西藏| 余干县| 盖州市| 石柱| 夏邑县| 临江市| 久治县| 朝阳市| 仲巴县| 南靖县| 巴南区| 金门县| 凤凰县| 邢台市| 焦作市| 甘洛县| 绥中县| 曲周县| 屏东市| 吴旗县| 洪雅县|