Sebbene l'adozione di Apache HBase per la creazione di applicazioni per utenti finali sia salita alle stelle, molte di queste applicazioni (e molte app in generale) non sono state ben testate. In questo post imparerai alcuni dei modi in cui questo test può essere eseguito facilmente.
Inizieremo con i test delle unità tramite JUnit, quindi passeremo all'utilizzo di Mockito e Apache MRUnit, quindi all'utilizzo di un mini-cluster HBase per i test di integrazione. (La stessa base di codice HBase viene testata tramite un mini-cluster, quindi perché non sfruttarlo anche per le applicazioni a monte?)
Come base per la discussione, supponiamo che tu abbia un oggetto di accesso ai dati HBase (DAO) che esegue il seguente inserimento in HBase. La logica potrebbe essere più complicata ovviamente, ma per il bene dell'esempio, questo fa il lavoro.
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 è un oggetto dati di base con getter e setter per rowkey, data1 e data2.
InsertRecord esegue un inserimento nella tabella HBase rispetto alla famiglia di colonne di CF, con CQ-1 e CQ-2 come qualificatori. Il metodo createPut compila semplicemente un Put e lo restituisce al metodo chiamante.
Utilizzo di JUnit
JUnit, che a questo punto è ben noto alla maggior parte degli sviluppatori Java, è facilmente applicabile a molte applicazioni HBase. Innanzitutto, aggiungi la dipendenza al tuo pom:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
Ora, all'interno della classe di prova:
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())); } }
Quello che hai fatto qui è assicurarti che il tuo metodo createPut crei, popola e restituisca un oggetto Put con i valori previsti.
Utilizzo di Mockito
Quindi, come si esegue il test unitario del metodo insertRecord sopra? Un approccio molto efficace è farlo con Mockito.
Innanzitutto, aggiungi Mockito come dipendenza al tuo pom:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency>
Quindi, in classe di prova:
@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"); } }
Qui hai popolato HBaseTestObj con "ROWKEY-1", "DATA-1", "DATA-2" come valori. Hai quindi utilizzato la tabella derisa e il DAO per inserire il record. Hai acquisito il Put che il DAO avrebbe inserito e verificato che rowkey, data1 e data2 sono quelli che ti aspetti che siano.
La chiave qui è gestire il pool htable e la creazione di istanze htable al di fuori del DAO. Ciò ti consente di deriderli in modo pulito e testare i Put come mostrato sopra. Allo stesso modo, ora puoi espandere tutte le altre operazioni come Ottieni, Scansiona, Elimina e così via.
Utilizzo di MRUnit
Dopo aver coperto i regolari unit test di accesso ai dati, passiamo ai lavori MapReduce che vanno contro le tabelle HBase.
Testare i lavori MR che vanno contro HBase è semplice come testare i normali lavori MapReduce. MRUnit rende davvero facile testare i lavori MapReduce inclusi quelli HBase.
Immagina di avere un lavoro MR che scrive su una tabella HBase, "MyTest", che ha una famiglia di colonne, "CF". Il riduttore di un tale lavoro potrebbe assomigliare a:
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); } }
Ora come si fa a testare il riduttore sopra in MRUnit? Innanzitutto, aggiungi MRUnit come dipendenza al tuo pom.
<dependency> <groupId>org.apache.mrunit</groupId> <artifactId>mrunit</artifactId> <version>1.0.0 </version> <scope>test</scope> </dependency>
Quindi, all'interno della classe di test, utilizza il ReduceDriver fornito da MRUnit come segue:
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 ); } }
Fondamentalmente, dopo una serie di elaborazioni in MyReducer, hai verificato che:
- L'output è quello che ti aspetti.
- Il Put inserito in HBase ha "RowKey-1" come chiave di riga.
- "DATADATA1DATA2" è il valore per la famiglia di colonne CF e il qualificatore di colonne CQ.
Puoi anche testare Mapper che ottengono dati da HBase in modo simile usando MapperDriver, o testare lavori MR che leggono da HBase, elaborano dati e scrivono su HDFS.
Utilizzo di un minicluster HBase
Ora vedremo come eseguire i test di integrazione. HBase viene fornito con HBaseTestingUtility, che semplifica la scrittura dei test di integrazione con un mini-cluster HBase. Per inserire le librerie corrette, sono necessarie le seguenti dipendenze nel tuo 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>
Vediamo ora come eseguire un test di integrazione per l'inserto MyDAO descritto nell'introduzione:
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()); }}
Qui hai creato un mini-cluster HBase e lo hai avviato. Hai quindi creato una tabella denominata "MyTest" con una famiglia di colonne, "CF". Hai inserito un record utilizzando il DAO che dovevi testare, hai eseguito un Get dalla stessa tabella e verificato che il DAO abbia inserito i record correttamente.
Lo stesso potrebbe essere fatto per casi d'uso molto più complicati insieme ai lavori di MR come quelli mostrati sopra. Puoi anche accedere ai minicluster HDFS e ZooKeeper creati durante la creazione di quello HBase, eseguire un processo MR, inviarlo in output su HBase e verificare i record inseriti.
Solo una breve nota di cautela:l'avvio di un mini-cluster richiede dai 20 ai 30 secondi e non può essere eseguito su Windows senza Cygwin. Tuttavia, poiché dovrebbero essere eseguiti solo periodicamente, il tempo di esecuzione più lungo dovrebbe essere accettabile.
È possibile trovare il codice di esempio per gli esempi precedenti su https://github.com/sitaula/HBaseTest. Buon test!