Även om användningen av Apache HBase för att bygga slutanvändarapplikationer har skjutit i höjden, har många av dessa applikationer (och många appar i allmänhet) inte testats väl. I det här inlägget kommer du att lära dig några av de sätt som denna testning enkelt kan göras på.
Vi börjar med enhetstestning via JUnit, går sedan vidare till att använda Mockito och Apache MRUnit, och sedan till att använda ett HBase-minikluster för integrationstestning. (Själva HBase-kodbasen testas via ett minikluster, så varför inte utnyttja det för uppströmsapplikationer också?)
Som underlag för diskussion, låt oss anta att du har ett HBase-dataåtkomstobjekt (DAO) som gör följande infogning i HBase. Logiken kan förstås vara mer komplicerad, men för exemplets skull gör detta jobbet.
public class MyHBaseDAO { public static void insertRecord(HTableInterface table, HBaseTestObj obj) throws Exception { Put put =createPut(obj); table.put(put); } privat statisk 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())); returnera sätta; }}
HBaseTestObj är ett grundläggande dataobjekt med getters och seters för rowkey, data1 och data2.
InsertRecord gör en infogning i HBase-tabellen mot kolumnfamiljen av CF, med CQ-1 och CQ-2 som kvalificerare. CreatePut-metoden fyller helt enkelt i en Put och returnerar den till anropsmetoden.
Använda JUnit
JUnit, som är välkänt för de flesta Java-utvecklare vid det här laget, är lätt att applicera på många HBase-applikationer. Lägg först till beroendet till din pom:
junit junit 4.11 test
Nu, inom testklassen:
public class TestMyHbaseDAOData { @Test public void testCreatePut() kastar undantag { 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())); } }
Vad du gjorde här var att se till att din createPut-metod skapar, fyller i och returnerar ett Put-objekt med förväntade värden.
Använda Mockito
Så hur går du tillväga för att enhetstesta ovanstående insertRecord-metoden? En mycket effektiv metod är att göra det med Mockito.
Lägg först till Mockito som ett beroende till din pom:
org.mockito mockito-all 1.9.5 test
Sedan, i testklassen:
@RunWith(MockitoJUnitRunner.class)public class TestMyHBaseDAO{ @Mock privat HTableInterface-tabell; @Mock privat HTablePool hTablePool; @Captor privat ArgumentCaptor putCaptor; @Test public void testInsertRecord() kastar undantag { //return mock table när getTable kallas when(hTablePool.getTable("tabellnamn")).thenReturn(table); //skapa testobjekt och gör ett anrop till DAO som behöver testas HBaseTestObj obj =new HBaseTestObj(); obj.setRowKey("ROWKEY-1"); obj.setData1("DATA-1"); obj.setData2("DATA-2"); MyHBaseDAO.insertRecord(tabell, obj); verifiera(tabell).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"); }}
Här har du fyllt i HBaseTestObj med "ROWKEY-1", "DATA-1", "DATA-2" som värden. Du använde sedan den hånade tabellen och DAO för att infoga posten. Du fångade den Put som DAO skulle ha infogat och verifierade att radtangenten, data1 och data2 är vad du förväntar dig att de ska vara.
Nyckeln här är att hantera htable pool och htable instansskapande utanför DAO. Detta gör att du kan håna dem rent och testa Puts som visas ovan. På samma sätt kan du nu expandera till alla andra operationer som Hämta, Skanna, Ta bort och så vidare.
Använda MRUnit
Med regelbunden dataåtkomstenhetstestning täckt, låt oss vända oss till MapReduce-jobb som går emot HBase-tabeller.
Att testa MR-jobb som går emot HBase är lika enkelt som att testa vanliga MapReduce-jobb. MRUnit gör det väldigt enkelt att testa MapReduce-jobb inklusive HBase-jobben.
Föreställ dig att du har ett MR-jobb som skriver till en HBase-tabell, "MyTest", som har en kolumnfamilj, "CF". Reduceraren för ett sådant jobb kan se ut så här:
public class MyReducer utökar TableReducer{ public static final byte[] CF ="CF".getBytes(); public static final byte[] QUALIFIER ="CQ-1".getBytes(); public void reduce(Textnyckel, Iterable values, Context context) kastar IOException, InterruptedException {//en massa bearbetning för att extrahera data som ska infogas, i vårt fall kan vi säga att vi helt enkelt //lägger till alla poster vi tar emot från mapparen för just denna //nyckel och infoga en post i HBase StringBuffer data =new StringBuffer(); Put put =new Put(Bytes.toBytes(key.toString())); for (Text val:värden) { data =data.append(val); } put.add(CF, QUALIFIER, Bytes.toBytes(data.toString())); //skriv till HBase context.write(new ImmutableBytesWritable(Bytes.toBytes(key.toString())), put); } }
Hur går du nu tillväga för att enhetstesta ovanstående reducerare i MRUnit? Lägg först till MRUnit som ett beroende till din pom.
org.apache.mrunit mrunit 1.0.0 test
Sedan, inom testklassen, använd ReduceDriver som MRUnit tillhandahåller enligt nedan:
public class MyReducerTest { ReduceDriverreduceDriver; 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() kastar IOException { String strKey ="RowKey-1", strValue ="DATA", strValue1 ="DATA1", strValue2 ="DATA2"; List list =new ArrayList (); list.add(ny text(strValue)); list.add(ny text(strValue1)); list.add(ny text(strValue2)); //eftersom allt som reduceraren gör i vårt fall är att lägga till poster som mapparen //sänder den, bör vi få tillbaka följande String expectedOutput =strValue + strValue1 + strValue2; //Setup Input, härma vilken mapper som skulle ha skickat //till reducern och kör testet reduceDriver.withInput(new Text(strKey), list); //kör reduceringen och hämta dess utdata List > result =reduceDriver.run(); //extrahera nyckel från resultatet och verifiera assertEquals(Bytes.toString(result.get(0).getFirst().get()), strKey); //extrahera värde för CF/QUALIFIER och verifiera Sätt a =(Put)result.get(0).getSecond(); String c =Bytes.toString(a.get(CF, QUALIFIER).get(0).getValue()); assertEquals(expectedOutput,c ); }}
I grund och botten, efter en massa bearbetning i MyReducer, verifierade du att:
- Utgången är vad du förväntar dig.
- Put som infogas i HBase har "RowKey-1" som radnyckel.
- ”DATADATA1DATA2” är värdet för CF-kolumnfamiljen och CQ-kolumnkvalificeraren.
Du kan också testa Mappers som får data från HBase på liknande sätt med MapperDriver, eller testa MR-jobb som läser från HBase, bearbetar data och skriver till HDFS.
Använda ett HBase Mini-kluster
Nu ska vi titta på hur man går tillväga för integrationstestning. HBase levereras med HBaseTestingUtility, vilket gör det enkelt att skriva integrationstestning med ett HBase-minikluster. För att hämta in rätt bibliotek krävs följande beroenden i din pom:
org.apache.hadoop hadoop-common 2.0.0-cdh4.2.0 test-jar typ> test org.apache.hbase hbase 0.94.2-cdh4.2.0 test-jar test org.apache.hadoop hadoop-hdfs 2.0.0-cdh4.2.0 test-jar test org.apache.hadoop hadoop-hdfs 2.0.0-cdh4.2.0 test
Låt oss nu titta på hur man kör igenom ett integrationstest för MyDAO-inlägget som beskrivs i inledningen:
public class MyHBaseIntegrationTest {private static HBaseTestingUtility utility;byte[] CF ="CF".getBytes();byte[] QUALIFIER ="CQ-1".getBytes();@Beforepublic void setup() kastar Exception { utility =new HBaseTestingUtility(); utility.startMiniCluster();}@Test public void testInsert() kastar Undantag { 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(tabell, obj); Get get1 =new Get(Bytes.toBytes(obj.getRowKey())); get1.addColumn(CF, CQ1); Resultat resultat1 =table.get(get1); assertEquals(Bytes.toString(result1.getRow()), obj.getRowKey()); assertEquals(Bytes.toString(result1.värde()), obj.getData1()); Get2 =new Get(Bytes.toBytes(obj.getRowKey())); get2.addColumn(CF, CQ2); Resultat resultat2 =table.get(get2); assertEquals(Bytes.toString(result2.getRow()), obj.getRowKey()); assertEquals(Bytes.toString(result2.value()), obj.getData2()); }}
Här skapade du ett HBase-minikluster och startade det. Du skapade sedan en tabell som heter "MyTest" med en kolumnfamilj, "CF". Du infogade en post med den DAO du behövde testa, gjorde en Get från samma tabell och verifierade att DAO:n infogade poster korrekt.
Detsamma kan göras för mycket mer komplicerade användningsfall tillsammans med MR-jobb som de som visas ovan. Du kan också komma åt HDFS- och ZooKeeper-miniklustren som skapades när du skapade HBase-en, köra ett MR-jobb, mata ut det till HBase och verifiera de infogade posterna.
Bara en snabb notering:att starta upp ett minikluster tar 20 till 30 sekunder och kan inte göras på Windows utan Cygwin. Men eftersom de bara bör köras periodiskt bör den längre körtiden vara acceptabel.
Du kan hitta exempelkod för ovanstående exempel på https://github.com/sitaula/HBaseTest. Lycka till med testet!