Chociaż wdrożenie Apache HBase do tworzenia aplikacji dla użytkowników końcowych gwałtownie wzrosło, wiele z tych aplikacji (i ogólnie wiele aplikacji) nie zostało dobrze przetestowanych. W tym poście dowiesz się, w jaki sposób można łatwo przeprowadzić te testy.
Zaczniemy od testów jednostkowych za pośrednictwem JUnit, następnie przejdziemy do korzystania z Mockito i Apache MRUnit, a następnie do korzystania z miniklastra HBase do testów integracyjnych. (Sama baza kodu HBase jest testowana za pomocą mini-klastra, więc dlaczego nie wykorzystać tego również w przypadku aplikacji nadrzędnych?)
Jako podstawę do dyskusji załóżmy, że masz obiekt dostępu do danych HBase (DAO), który wykonuje następujące wstawienie do HBase. Logika może być oczywiście bardziej skomplikowana, ale dla przykładu to działa.
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 to podstawowy obiekt danych z getterami i seterami dla klucza wiersza, danych1 i danych2.
InsertRecord wstawia do tabeli HBase względem rodziny kolumn CF, z kwalifikatorami CQ-1 i CQ-2. Metoda createPut po prostu wypełnia Put i zwraca ją do metody wywołującej.
Korzystanie z JUnit
JUnit, który w tym momencie jest dobrze znany większości programistów Java, można łatwo zastosować w wielu aplikacjach HBase. Najpierw dodaj zależność do swojego POM:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
Teraz w klasie testowej:
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())); } }
To, co zrobiłeś tutaj, to upewnienie się, że Twoja metoda createPut tworzy, wypełnia i zwraca obiekt Put z oczekiwanymi wartościami.
Korzystanie z Mockito
Jak więc idzie o testowanie jednostkowe powyższej metody insertRecord? Jednym z bardzo skutecznych sposobów jest zrobienie tego z Mockito.
Najpierw dodaj Mockito jako zależność do swojego pom:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency>
Następnie w klasie testowej:
@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"); } }
Tutaj wypełniłeś HBaseTestObj wartościami „ROWKEY-1”, „DATA-1”, „DATA-2”. Następnie użyłeś sfałszowanej tabeli i DAO, aby wstawić rekord. Przechwyciłeś Put, który DAO wstawiłby, i zweryfikowałeś, że klucz wiersza, dane1 i dane2 są tym, czego oczekujesz.
Kluczem jest tutaj zarządzanie pulą htable i tworzeniem instancji htable poza DAO. Pozwala to na wykpiwanie ich w czysty sposób i testowanie Put, jak pokazano powyżej. Podobnie możesz teraz rozwinąć wszystkie inne operacje, takie jak Pobierz, Skanuj, Usuń itd.
Korzystanie z MRUnit
Mając omówione regularne testowanie jednostek dostępu do danych, zwróćmy się w stronę zadań MapReduce, które działają w oparciu o tabele HBase.
Testowanie zadań MR, które są zgodne z HBase, jest tak proste, jak testowanie zwykłych zadań MapReduce. MRUnit bardzo ułatwia testowanie zadań MapReduce, w tym zadań HBase.
Wyobraź sobie, że masz zadanie MR, które zapisuje w tabeli HBase „MyTest”, która ma jedną rodzinę kolumn „CF”. Reduktor takiej pracy mógłby wyglądać tak:
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); } }
Teraz, jak idziesz o testowanie jednostkowe powyższego reduktora w MRUnit? Najpierw dodaj MRUnit jako zależność do swojego POM.
<dependency> <groupId>org.apache.mrunit</groupId> <artifactId>mrunit</artifactId> <version>1.0.0 </version> <scope>test</scope> </dependency>
Następnie w klasie testowej użyj narzędzia ReduceDriver, które zapewnia MRUnit, jak poniżej:
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 ); } }
Zasadniczo, po kilku operacjach przetwarzania w MyReducer, potwierdziłeś, że:
- Wynik jest taki, jakiego oczekujesz.
- Ustawienie Put wstawione do HBase ma „RowKey-1” jako klawisz rowka.
- „DATADATA1DATA2” to wartość rodziny kolumn CF i kwalifikatora kolumny CQ.
Możesz także testować Mappery, które pobierają dane z HBase w podobny sposób, używając MapperDriver, lub testować zadania MR, które odczytują z HBase, przetwarzają dane i zapisują w HDFS.
Korzystanie z miniklastra HBase
Teraz przyjrzymy się, jak przejść do testowania integracji. HBase jest dostarczany z HBaseTestingUtility, co ułatwia pisanie testów integracji za pomocą miniklastra HBase. Aby pobrać odpowiednie biblioteki, w Twoim pom wymagane są następujące zależności:
<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>
Przyjrzyjmy się teraz, jak przeprowadzić test integracyjny dla wstawki MyDAO opisanej we wstępie:
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()); }}
Tutaj utworzyłeś mini-klaster HBase i uruchomiłeś go. Następnie utworzyłeś tabelę o nazwie „MyTest” z jedną rodziną kolumn „CF”. Wstawiłeś rekord za pomocą DAO, który chciałeś przetestować, wykonałeś Get z tej samej tabeli i sprawdziłeś, czy DAO wstawił rekordy poprawnie.
To samo można zrobić w przypadku znacznie bardziej skomplikowanych przypadków użycia wraz z zadaniami MR, takimi jak te pokazane powyżej. Możesz także uzyskać dostęp do miniklastrów HDFS i ZooKeeper utworzonych podczas tworzenia HBase, uruchomić zadanie MR, przesłać je do HBase i zweryfikować wstawione rekordy.
Tylko krótka uwaga:uruchomienie miniklastra zajmuje od 20 do 30 sekund i nie można tego zrobić w systemie Windows bez Cygwin. Jednak ponieważ powinny być uruchamiane tylko okresowo, dłuższy czas działania powinien być akceptowalny.
Przykładowy kod dla powyższych przykładów można znaleźć na https://github.com/sitaula/HBaseTest. Miłego testowania!