4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
23 = Unit Testing HBase Applications
30 This chapter discusses unit testing your HBase application using JUnit, Mockito, MRUnit, and HBaseTestingUtility.
31 Much of the information comes from link:http://blog.cloudera.com/blog/2013/09/how-to-test-hbase-applications-using-popular-tools/[a community blog post about testing HBase applications].
32 For information on unit tests for HBase itself, see <<hbase.tests,hbase.tests>>.
36 HBase uses link:http://junit.org[JUnit] for unit tests
38 This example will add unit tests to the following example class:
43 public class MyHBaseDAO {
45 public static void insertRecord(Table.getTable(table), HBaseTestObj obj)
47 Put put = createPut(obj);
51 private static Put createPut(HBaseTestObj obj) {
52 Put put = new Put(Bytes.toBytes(obj.getRowKey()));
53 put.add(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1"),
54 Bytes.toBytes(obj.getData1()));
55 put.add(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2"),
56 Bytes.toBytes(obj.getData2()));
62 The first step is to add JUnit dependencies to your Maven POM file:
68 <groupId>junit</groupId>
69 <artifactId>junit</artifactId>
70 <version>4.11</version>
75 Next, add some unit tests to your code.
76 Tests are annotated with `@Test`.
77 Here, the unit tests are in bold.
82 public class TestMyHbaseDAOData {
84 public void testCreatePut() throws Exception {
85 HBaseTestObj obj = new HBaseTestObj();
86 obj.setRowKey("ROWKEY-1");
87 obj.setData1("DATA-1");
88 obj.setData2("DATA-2");
89 Put put = MyHBaseDAO.createPut(obj);
90 assertEquals(obj.getRowKey(), Bytes.toString(put.getRow()));
91 assertEquals(obj.getData1(), Bytes.toString(put.get(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1")).get(0).getValue()));
92 assertEquals(obj.getData2(), Bytes.toString(put.get(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2")).get(0).getValue()));
97 These tests ensure that your `createPut` method creates, populates, and returns a `Put` object with expected values.
98 Of course, JUnit can do much more than this.
99 For an introduction to JUnit, see https://github.com/junit-team/junit/wiki/Getting-started.
104 Mockito is a mocking framework.
105 It goes further than JUnit by allowing you to test the interactions between objects without having to replicate the entire environment.
106 You can read more about Mockito at its project site, https://code.google.com/p/mockito/.
108 You can use Mockito to do unit testing on smaller units.
109 For instance, you can mock a `org.apache.hadoop.hbase.Server` instance or a `org.apache.hadoop.hbase.master.MasterServices` interface reference rather than a full-blown `org.apache.hadoop.hbase.master.HMaster`.
111 This example builds upon the example code in <<unit.tests,unit.tests>>, to test the `insertRecord` method.
113 First, add a dependency for Mockito to your Maven POM file.
119 <groupId>org.mockito</groupId>
120 <artifactId>mockito-core</artifactId>
121 <version>2.1.0</version>
126 Next, add a `@RunWith` annotation to your test class, to direct it to use Mockito.
131 @RunWith(MockitoJUnitRunner.class)
132 public class TestMyHBaseDAO{
134 Configuration config = HBaseConfiguration.create();
136 Connection connection = ConnectionFactory.createConnection(config);
140 private ArgumentCaptor putCaptor;
143 public void testInsertRecord() throws Exception {
144 //return mock table when getTable is called
145 when(connection.getTable(TableName.valueOf("tablename")).thenReturn(table);
146 //create test object and make a call to the DAO that needs testing
147 HBaseTestObj obj = new HBaseTestObj();
148 obj.setRowKey("ROWKEY-1");
149 obj.setData1("DATA-1");
150 obj.setData2("DATA-2");
151 MyHBaseDAO.insertRecord(table, obj);
152 verify(table).put(putCaptor.capture());
153 Put put = putCaptor.getValue();
155 assertEquals(Bytes.toString(put.getRow()), obj.getRowKey());
156 assert(put.has(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1")));
157 assert(put.has(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2")));
158 assertEquals(Bytes.toString(put.get(Bytes.toBytes("CF"),Bytes.toBytes("CQ-1")).get(0).getValue()), "DATA-1");
159 assertEquals(Bytes.toString(put.get(Bytes.toBytes("CF"),Bytes.toBytes("CQ-2")).get(0).getValue()), "DATA-2");
164 This code populates `HBaseTestObj` with ``ROWKEY-1'', ``DATA-1'', ``DATA-2'' as values.
165 It then inserts the record into the mocked table.
166 The Put that the DAO would have inserted is captured, and values are tested to verify that they are what you expected them to be.
168 The key here is to manage Connection and Table instance creation outside the DAO.
169 This allows you to mock them cleanly and test Puts as shown above.
170 Similarly, you can now expand into other operations such as Get, Scan, or Delete.
174 link:https://mrunit.apache.org/[Apache MRUnit] is a library that allows you to unit-test MapReduce jobs.
175 You can use it to test HBase jobs in the same way as other MapReduce jobs.
177 Given a MapReduce job that writes to an HBase table called `MyTest`, which has one column family called `CF`, the reducer of such a job could look like the following:
182 public class MyReducer extends TableReducer<Text, Text, ImmutableBytesWritable> {
183 public static final byte[] CF = "CF".getBytes();
184 public static final byte[] QUALIFIER = "CQ-1".getBytes();
185 public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
186 //bunch of processing to extract data to be inserted, in our case, let's say we are simply
187 //appending all the records we receive from the mapper for this particular
188 //key and insert one record into HBase
189 StringBuffer data = new StringBuffer();
190 Put put = new Put(Bytes.toBytes(key.toString()));
191 for (Text val : values) {
192 data = data.append(val);
194 put.add(CF, QUALIFIER, Bytes.toBytes(data.toString()));
196 context.write(new ImmutableBytesWritable(Bytes.toBytes(key.toString())), put);
201 To test this code, the first step is to add a dependency to MRUnit to your Maven POM file.
207 <groupId>org.apache.mrunit</groupId>
208 <artifactId>mrunit</artifactId>
209 <version>1.0.0 </version>
214 Next, use the ReducerDriver provided by MRUnit, in your Reducer job.
219 public class MyReducerTest {
220 ReduceDriver<Text, Text, ImmutableBytesWritable, Writable> reduceDriver;
221 byte[] CF = "CF".getBytes();
222 byte[] QUALIFIER = "CQ-1".getBytes();
225 public void setUp() {
226 MyReducer reducer = new MyReducer();
227 reduceDriver = ReduceDriver.newReduceDriver(reducer);
231 public void testHBaseInsert() throws IOException {
232 String strKey = "RowKey-1", strValue = "DATA", strValue1 = "DATA1",
234 List<Text> list = new ArrayList<Text>();
235 list.add(new Text(strValue));
236 list.add(new Text(strValue1));
237 list.add(new Text(strValue2));
238 //since in our case all that the reducer is doing is appending the records that the mapper
239 //sends it, we should get the following back
240 String expectedOutput = strValue + strValue1 + strValue2;
241 //Setup Input, mimic what mapper would have passed
242 //to the reducer and run test
243 reduceDriver.withInput(new Text(strKey), list);
244 //run the reducer and get its output
245 List<Pair<ImmutableBytesWritable, Writable>> result = reduceDriver.run();
247 //extract key from result and verify
248 assertEquals(Bytes.toString(result.get(0).getFirst().get()), strKey);
250 //extract value for CF/QUALIFIER and verify
251 Put a = (Put)result.get(0).getSecond();
252 String c = Bytes.toString(a.get(CF, QUALIFIER).get(0).getValue());
253 assertEquals(expectedOutput,c );
259 Your MRUnit test verifies that the output is as expected, the Put that is inserted into HBase has the correct value, and the ColumnFamily and ColumnQualifier have the correct values.
261 MRUnit includes a MapperDriver to test mapping jobs, and you can use MRUnit to test other operations, including reading from HBase, processing data, or writing to HDFS,
263 == Integration Testing with an HBase Mini-Cluster
265 HBase ships with HBaseTestingUtility, which makes it easy to write integration tests using a [firstterm]_mini-cluster_.
266 The first step is to add some dependencies to your Maven POM file.
267 Check the versions to be sure they are appropriate.
272 <hbase.version>2.0.0-SNAPSHOT</hbase.version>
277 <groupId>org.apache.hbase</groupId>
278 <artifactId>hbase-testing-util</artifactId>
279 <version>${hbase.version}</version>
285 This code represents an integration test for the MyDAO insert shown in <<unit.tests,unit.tests>>.
290 public class MyHBaseIntegrationTest {
291 private static HBaseTestingUtility utility;
292 byte[] CF = "CF".getBytes();
293 byte[] CQ1 = "CQ-1".getBytes();
294 byte[] CQ2 = "CQ-2".getBytes();
297 public void setup() throws Exception {
298 utility = new HBaseTestingUtility();
299 utility.startMiniCluster();
303 public void testInsert() throws Exception {
304 Table table = utility.createTable(Bytes.toBytes("MyTest"), CF);
305 HBaseTestObj obj = new HBaseTestObj();
306 obj.setRowKey("ROWKEY-1");
307 obj.setData1("DATA-1");
308 obj.setData2("DATA-2");
309 MyHBaseDAO.insertRecord(table, obj);
310 Get get1 = new Get(Bytes.toBytes(obj.getRowKey()));
311 get1.addColumn(CF, CQ1);
312 Result result1 = table.get(get1);
313 assertEquals(Bytes.toString(result1.getRow()), obj.getRowKey());
314 assertEquals(Bytes.toString(result1.value()), obj.getData1());
315 Get get2 = new Get(Bytes.toBytes(obj.getRowKey()));
316 get2.addColumn(CF, CQ2);
317 Result result2 = table.get(get2);
318 assertEquals(Bytes.toString(result2.getRow()), obj.getRowKey());
319 assertEquals(Bytes.toString(result2.value()), obj.getData2());
324 This code creates an HBase mini-cluster and starts it.
325 Next, it creates a table called `MyTest` with one column family, `CF`.
326 A record is inserted, a Get is performed from the same table, and the insertion is verified.
328 NOTE: Starting the mini-cluster takes about 20-30 seconds, but that should be appropriate for integration testing.
330 See the paper at link:http://blog.sematext.com/2010/08/30/hbase-case-study-using-hbasetestingutility-for-local-testing-development/[HBase Case-Study: Using HBaseTestingUtility for Local Testing and
331 Development] (2010) for more information about HBaseTestingUtility.