エンドユーザーアプリケーションを構築するためのApacheHBaseの採用は急増していますが、それらのアプリケーションの多く(および一般的には多くのアプリ)は十分にテストされていません。この投稿では、このテストを簡単に実行できるいくつかの方法を学びます。
JUnitを介した単体テストから始め、次にMockitoとApache MRUnitの使用に移り、統合テストにHBaseミニクラスターを使用します。 (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、およびdata2のゲッターとセッターを備えた基本的なデータオブジェクトです。
insertRecordは、CQ-1とCQ-2を修飾子として、CFの列ファミリーに対してHBaseテーブルに挿入します。 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の使用
では、上記のinsertRecordメソッドの単体テストをどのように行いますか?非常に効果的なアプローチの1つは、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"); } }
ここでは、HBaseTestObjに「ROWKEY-1」、「DATA-1」、「DATA-2」を値として入力しました。次に、モックテーブルとDAOを使用してレコードを挿入しました。 DAOが挿入したであろうプットをキャプチャし、行キー、data1、およびdata2が期待どおりであることを確認しました。
ここで重要なのは、DAOの外部でhtableプールとhtableインスタンスの作成を管理することです。これにより、それらをきれいにモックして、上記のようにPutをテストできます。同様に、Get、Scan、Deleteなどの他のすべての操作に拡張できるようになりました。
MRUnitの使用
定期的なデータアクセスユニットテストについて説明したので、HBaseテーブルに反するMapReduceジョブに目を向けましょう。
HBaseに反するMRジョブのテストは、通常のMapReduceジョブのテストと同じくらい簡単です。 MRUnitを使用すると、HBaseジョブを含むMapReduceジョブを非常に簡単にテストできます。
1つの列ファミリー「CF」を持つHBaseテーブル「MyTest」に書き込むMRジョブがあるとします。そのような仕事のレデューサーは次のようになります:
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で上記のレデューサーのユニットテストをどのように行いますか?まず、MRUnitを依存関係としてpomに追加します。
<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で一連の処理を行った後、次のことを確認しました。
- 出力は期待どおりです。
- HBaseに挿入されるPutには、行キーとして「RowKey-1」があります。
- 「DATADATA1DATA2」は、CF列ファミリーとCQ列修飾子の値です。
MapperDriverを使用して同様の方法でHBaseからデータを取得するMappersをテストしたり、HBaseから読み取り、データを処理し、HDFSに書き込むMRジョブをテストしたりすることもできます。
HBaseミニクラスターの使用
次に、統合テストを行う方法を見ていきます。 HBaseにはHBaseTestingUtilityが付属しているため、HBaseミニクラスターを使用した統合テストの記述が簡単になります。正しいライブラリを取得するには、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ミニクラスターを作成して開始しました。次に、1つの列ファミリー「CF」を持つ「MyTest」というテーブルを作成しました。テストに必要なDAOを使用してレコードを挿入し、同じテーブルからGetを実行し、DAOがレコードを正しく挿入したことを確認しました。
上記のようなMRジョブとともに、はるかに複雑なユースケースでも同じことができます。また、HBaseの作成中に作成されたHDFSおよびZooKeeperミニクラスターにアクセスし、MRジョブを実行し、それをHBaseに出力して、挿入されたレコードを確認することもできます。
注意点として、ミニクラスターの起動には20〜30秒かかり、CygwinがないWindowsでは実行できません。ただし、定期的に実行する必要があるため、実行時間は長くてもかまいません。
上記の例のサンプルコードは、https://github.com/sitaula/HBaseTestにあります。ハッピーテスト!