1. 程式人生 > >使用常用工具測試HBase應用

使用常用工具測試HBase應用

雖然業界採用Apache HBase來構建終端使用者應用的範圍越來越多,但是許多這種應用並沒有經過良好的測試。通過這篇文章,你可以瞭解到有關這方面的一些容易實現的測試方法。

我們首先以JUnit為例, 然後是Mockito 和Apache MRUnit, 接著會使用HBase的一個微型叢集來做整合測試。(HBase自身的程式碼也是通過一個微型的叢集來測試的, 所以對於上游的應用為什麼不能這樣測試呢?)

作為探討的基礎,我們假設你建立了用於將資料插入到HBase 的資料訪問物件(DAO)。實際的邏輯可能很複雜,但為了演示用例,以下簡單的程式碼也可以完成基本的功能:

public class MyHBaseDAO {

                public static void insertRecord(HTableInterface table, HBaseTestObj obj)
        throws Exception {
                        Put put = createPut(obj);
                        table.put(put);
                }

                private static Put createPut(HBaseTestObj obj) {
                        Put put = new Put(Bytes.toBytes(obj.getRowKey()));
                        put.add(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1"),
                                Bytes.toBytes(obj.getData1()));
                        put.add(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2"),
                                Bytes.toBytes(obj.getData2()));
                        return put;
                }
}

HBaseTestObj是一個含有成員rowkey, data1, and data2及其getter和setter的物件。

方法insertRecord向HBase表插入了列族為CF, 列為CQ-1,CQ-2的記錄。方法createPut 簡單的包裝了Put資料並返回給呼叫者。

使用JUnit

對於大部分Java開發者來說都很熟悉的JUnit, 可以容易的應用到HBase大部分程式中。 首先,在pom中新增依賴庫:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>

然後在測試類中使用:

public class TestMyHbaseDAOData {
                @Test
                public void testCreatePut() throws Exception {
                HBaseTestObj obj = new HBaseTestObj();
                obj.setRowKey("ROWKEY-1");
                obj.setData1("DATA-1");
                obj.setData2("DATA-2");
                Put put = MyHBaseDAO.createPut(obj);
                assertEquals(obj.getRowKey(), Bytes.toString(put.getRow()));
                assertEquals(obj.getData1(), Bytes.toString(put.get(Bytes.toBytes("CF"),
  Bytes.toBytes("CQ-1")).get(0).getValue()));
                assertEquals(obj.getData2(), Bytes.toString(put.get(Bytes.toBytes("CF"),
  Bytes.toBytes("CQ-2")).get(0).getValue()));
                }
  }

上面所寫的程式碼就是為了保證方法createPut能正常的建立,填充,和返回物件Put。

使用Mockito

怎麼實現像JUnit那樣的功能來測試方法insertRecord? 使用Mockito可以這樣做:

首先在pom中新增Mockito依賴庫:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

然後在測試類中增加如下程式碼:

@RunWith(MockitoJUnitRunner.class)
public class TestMyHBaseDAO{
  @Mock 
  private HTableInterface table;
  @Mock
  private HTablePool hTablePool;
  @Captor
  private ArgumentCaptor putCaptor;

  @Test
  public void testInsertRecord() throws Exception {
    //return mock table when getTable is called
    when(hTablePool.getTable("tablename")).thenReturn(table);
    //create test object and make a call to the DAO that needs testing
    HBaseTestObj obj = new HBaseTestObj();
    obj.setRowKey("ROWKEY-1");
    obj.setData1("DATA-1");
    obj.setData2("DATA-2");
    MyHBaseDAO.insertRecord(table, obj);
    verify(table).put(putCaptor.capture());
    Put put = putCaptor.getValue();
  
    assertEquals(Bytes.toString(put.getRow()), obj.getRowKey());
    assert(put.has(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1")));
    assert(put.has(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2")));
    assertEquals(Bytes.toString(put.get(Bytes.toBytes("CF"),
Bytes.toBytes("CQ-1")).get(0).getValue()), "DATA-1");
    assertEquals(Bytes.toString(put.get(Bytes.toBytes("CF"),
Bytes.toBytes("CQ-2")).get(0).getValue()), "DATA-2");
  }
}

上述程式碼使用了“ROWKEY-1”, “DATA-1”, “DATA-2”來填充了HBaseTestObj,然後用table介面和DAO來將它插入到表中。此過程將捕捉DAO用來inert操作的物件Put和檢查rowkey, data1及 data2的值是否符合期望。

這裡的重點是需要管理HTable pool和在DAO之外建立的HTable物件。這樣會讓你清晰的進行mock(建立虛擬物件)並像上面一樣測試Put物件。近似的也可以這樣測試Get, Scan, Delete等操作。

使用MRUnit

使用常規的資料訪問來覆蓋單元測試,讓我們針對HBase表來實現MapReduce作業。

可以像測試MapReduce常規任務來測試HBase的MapReduce作業,MRUnit使得很容易完成這樣的單元測試。

假設你有一個往HBase表寫資料MR作業“MyTest”, 它的列族為“CF”。作業的reducer部分像以下這樣:

public class MyReducer extends TableReducer<Text, Text, ImmutableBytesWritable> {
   public static final byte[] CF = "CF".getBytes();
   public static final byte[] QUALIFIER = "CQ-1".getBytes();
  public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
     //bunch of processing to extract data to be inserted, in our case, lets say we are simply
     //appending all the records we receive from the mapper for this particular
     //key and insert one record into HBase
     StringBuffer data = new StringBuffer();
     Put put = new Put(Bytes.toBytes(key.toString()));
     for (Text val : values) {
         data = data.append(val);
     }
     put.add(CF, QUALIFIER, Bytes.toBytes(data.toString()));
     //write to HBase
     context.write(new ImmutableBytesWritable(Bytes.toBytes(key.toString())), put);
   }
 }

現在你如何在MRUnit中使用單元測試來測試reducer呢? 首先,在pom中新增MRUnit依賴庫.

<dependency>
   <groupId>org.apache.mrunit</groupId>
   <artifactId>mrunit</artifactId>
   <version>1.0.0 </version>
   <scope>test</scope>
</dependency>

接著,在測試類中像下面這樣呼叫MRUnit提供的ReduceDriver:

public class MyReducerTest {
    ReduceDriver<Text, Text, ImmutableBytesWritable, Writable> reduceDriver;
    byte[] CF = "CF".getBytes();
    byte[] QUALIFIER = "CQ-1".getBytes();

    @Before
    public void setUp() {
      MyReducer reducer = new MyReducer();
      reduceDriver = ReduceDriver.newReduceDriver(reducer);
    }
  
   @Test
   public void testHBaseInsert() throws IOException {
      String strKey = "RowKey-1", strValue = "DATA", strValue1 = "DATA1", 
strValue2 = "DATA2";
      List<Text> list = new ArrayList<Text>();
      list.add(new Text(strValue));
      list.add(new Text(strValue1));
      list.add(new Text(strValue2));
      //since in our case all that the reducer is doing is appending the records that the mapper   
      //sends it, we should get the following back
      String expectedOutput = strValue + strValue1 + strValue2;
     //Setup Input, mimic what mapper would have passed
      //to the reducer and run test
      reduceDriver.withInput(new Text(strKey), list);
      //run the reducer and get its output
      List<Pair<ImmutableBytesWritable, Writable>> result = reduceDriver.run();
    
      //extract key from result and verify
      assertEquals(Bytes.toString(result.get(0).getFirst().get()), strKey);
    
      //extract value for CF/QUALIFIER and verify
      Put a = (Put)result.get(0).getSecond();
      String c = Bytes.toString(a.get(CF, QUALIFIER).get(0).getValue());
      assertEquals(expectedOutput,c );
   }

}

在MyReducer中進行的一系列過程,你會驗證:

  • 你所期望得到的輸出結果。
  • 物件Put以 “RowKey-1″為鍵插入到HBAse表中。
  • “DATADATA1DATA2″是列族CF和列CQ的值。

你也可以類似的使用MapperDriver來測試從HBase中獲取資料的Mapper,或者測試從HBase讀取、處理和寫入資料到HDFS的MR作業。

使用HBase Mini-cluster

現在我們來看一下怎樣實現整合測試。HBase附帶了HBaseTestingUtility, 這個用於在簡單的mini-cluster環境中編寫整合測試。為了引用正確的庫,在pom中需要新增如下依賴:

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>2.0.0-cdh4.2.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase</artifactId>
    <version>0.94.2-cdh4.2.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>
        
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.0.0-cdh4.2.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.0.0-cdh4.2.0</version>
    <scope>test</scope>
</dependency>

現在看一下怎樣來為MyDAO的插入操作執行一個整合測試:

public class MyHBaseIntegrationTest {
private static HBaseTestingUtility utility;
byte[] CF = "CF".getBytes();
byte[] QUALIFIER = "CQ-1".getBytes();

@Before
public void setup() throws Exception {
        utility = new HBaseTestingUtility();
        utility.startMiniCluster();
}

@Test
    public void testInsert() throws Exception {
         HTableInterface table = utility.createTable(Bytes.toBytes("MyTest"),
                         Bytes.toBytes("CF"));
         HBaseTestObj obj = new HBaseTestObj();
         obj.setRowKey("ROWKEY-1");
         obj.setData1("DATA-1");
         obj.setData2("DATA-2");
         MyHBaseDAO.insertRecord(table, obj);
         Get get1 = new Get(Bytes.toBytes(obj.getRowKey()));
         get1.addColumn(CF, CQ1);
         Result result1 = table.get(get1);
         assertEquals(Bytes.toString(result1.getRow()), obj.getRowKey());
         assertEquals(Bytes.toString(result1.value()), obj.getData1());
         Get get2 = new Get(Bytes.toBytes(obj.getRowKey()));
         get2.addColumn(CF, CQ2);
         Result result2 = table.get(get2);
         assertEquals(Bytes.toString(result2.getRow()), obj.getRowKey());
         assertEquals(Bytes.toString(result2.value()), obj.getData2());
    }}

上面程式碼建立了一個HBase微型叢集並啟動它,然後建立了名為“MyTest”的其列族為"CF"的HBase表,接著使用DAO插入一條記錄、再從該表執行Get操作,驗證DAO是否正確的插入記錄。

同樣可以測試更加複雜的MR作業。也可以在建立HBase叢集,執行MR作業,輸出資料到HBase表,驗證插入的資料時訪問HDFS及ZooKeeper mini-cluster。

注意:啟動一個mini-cluster需要20到30秒,在Windows下需要安裝Cygwin。然而它們應該只是週期性的執行,更長的時間也是可以接受的。