HBASE-26921 Rewrite the counting cells part in TestMultiVersions (#4316)
[hbase.git] / src / main / asciidoc / _chapters / unit_testing.adoc
blob3329a75b68c48d6c93371260b54d78e32514ea80
1 ////
2 /**
3  *
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
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
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.
19  */
20 ////
22 [[unit.tests]]
23 = Unit Testing HBase Applications
24 :doctype: book
25 :numbered:
26 :toc: left
27 :icons: font
28 :experimental:
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>>.
34 == JUnit
36 HBase uses link:http://junit.org[JUnit] for unit tests
38 This example will add unit tests to the following example class:
40 [source,java]
41 ----
43 public class MyHBaseDAO {
45     public static void insertRecord(Table.getTable(table), HBaseTestObj obj)
46     throws Exception {
47         Put put = createPut(obj);
48         table.put(put);
49     }
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()));
57         return put;
58     }
60 ----
62 The first step is to add JUnit dependencies to your Maven POM file:
64 [source,xml]
65 ----
67 <dependency>
68     <groupId>junit</groupId>
69     <artifactId>junit</artifactId>
70     <version>4.11</version>
71     <scope>test</scope>
72 </dependency>
73 ----
75 Next, add some unit tests to your code.
76 Tests are annotated with `@Test`.
77 Here, the unit tests are in bold.
79 [source,java]
80 ----
82 public class TestMyHbaseDAOData {
83   @Test
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()));
93   }
95 ----
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.
101 [[mockito]]
102 == Mockito
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.
115 [source,xml]
116 ----
118 <dependency>
119     <groupId>org.mockito</groupId>
120     <artifactId>mockito-core</artifactId>
121     <version>2.1.0</version>
122     <scope>test</scope>
123 </dependency>
124 ----
126 Next, add a `@RunWith` annotation to your test class, to direct it to use Mockito.
128 [source,java]
129 ----
131 @RunWith(MockitoJUnitRunner.class)
132 public class TestMyHBaseDAO{
133   @Mock
134   Configuration config = HBaseConfiguration.create();
135   @Mock
136   Connection connection = ConnectionFactory.createConnection(config);
137   @Mock
138   private Table table;
139   @Captor
140   private ArgumentCaptor putCaptor;
142   @Test
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");
160   }
162 ----
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.
172 == MRUnit
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:
179 [source,java]
180 ----
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);
193      }
194      put.add(CF, QUALIFIER, Bytes.toBytes(data.toString()));
195      //write to HBase
196      context.write(new ImmutableBytesWritable(Bytes.toBytes(key.toString())), put);
197    }
199 ----
201 To test this code, the first step is to add a dependency to MRUnit to your Maven POM file.
203 [source,xml]
204 ----
206 <dependency>
207    <groupId>org.apache.mrunit</groupId>
208    <artifactId>mrunit</artifactId>
209    <version>1.0.0 </version>
210    <scope>test</scope>
211 </dependency>
212 ----
214 Next, use the ReducerDriver provided by MRUnit, in your Reducer job.
216 [source,java]
217 ----
219 public class MyReducerTest {
220     ReduceDriver<Text, Text, ImmutableBytesWritable, Writable> reduceDriver;
221     byte[] CF = "CF".getBytes();
222     byte[] QUALIFIER = "CQ-1".getBytes();
224     @Before
225     public void setUp() {
226       MyReducer reducer = new MyReducer();
227       reduceDriver = ReduceDriver.newReduceDriver(reducer);
228     }
230    @Test
231    public void testHBaseInsert() throws IOException {
232       String strKey = "RowKey-1", strValue = "DATA", strValue1 = "DATA1",
233 strValue2 = "DATA2";
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 );
254    }
257 ----
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.
269 [source,xml]
270 ----
271 <properties>
272   <hbase.version>2.0.0-SNAPSHOT</hbase.version>
273 </properties>
275 <dependencies>
276   <dependency>
277     <groupId>org.apache.hbase</groupId>
278     <artifactId>hbase-testing-util</artifactId>
279     <version>${hbase.version}</version>
280     <scope>test</scope>
281   </dependency>
282 </dependencies>
283 ----
285 This code represents an integration test for the MyDAO insert shown in <<unit.tests,unit.tests>>.
287 [source,java]
288 ----
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();
296     @Before
297     public void setup() throws Exception {
298         utility = new HBaseTestingUtility();
299         utility.startMiniCluster();
300     }
302     @Test
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());
320     }
322 ----
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.