HBase 애플리케이션 단위 테스트
HBase 애플리케이션 단위 테스트
이 문서는 HBase 애플리케이션을 JUnit, Mockito, MRUnit, 그리고 HBase mini-cluster로 테스트하는 방법을 설명해요. 대부분의 내용은 HBase 애플리케이션 테스트에 대한 커뮤니티 블로그 게시물에서 나왔어요. HBase 자체의 단위 테스트에 대한 정보는 hbase.tests를 참고하세요.
출처: 문서
본문
많은 정보가 HBase 애플리케이션 테스트에 대한 커뮤니티 블로그 게시물에서 나왔어요. HBase 자체의 단위 테스트에 대한 정보는 hbase.tests를 참고하세요.
HBase 2.5.0부터 HBaseTestingUtility는 deprecated이며 HBase 내부에서 UT를 작성할 때만 사용해야 해요. 최종 사용자는 org.apache.hadoop.hbase.testing.TestingHBaseCluster를 사용해야 해요.
JUnit
HBase는 단위 테스트에 JUnit을 사용해요.
이 예시는 다음 예시 클래스에 단위 테스트를 추가할 거예요.
public class MyHBaseDAO {
public static void insertRecord(Table.getTable(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;
}
}
첫 번째 단계는 Maven POM 파일에 JUnit 의존성을 추가하는 것이에요.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
다음으로 코드에 몇 가지 단위 테스트를 추가하세요. 테스트는 @Test로 주석 처리돼요. 여기서 단위 테스트는 굵게 표시돼요.
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()));
}
}
이 테스트들은 createPut 메서드가 기대한 값으로 Put 객체를 만들고, 채우고, 반환하는지 확인해요. 물론 JUnit은 이것보다 훨씬 더 많은 것을 할 수 있어요. JUnit 소개는 https://github.com/junit-team/junit/wiki/Getting-started를 참고하세요.
Mockito
Mockito는 mocking 프레임워크예요. 전체 환경을 복제하지 않고도 객체 간의 상호작용을 테스트할 수 있게 해 줘서 JUnit보다 더 나아가요. Mockito에 대한 자세한 내용은 프로젝트 사이트 https://code.google.com/p/mockito/에서 읽을 수 있어요.
Mockito를 사용해 더 작은 단위에서 단위 테스트를 할 수 있어요. 예를 들어 전체 org.apache.hadoop.hbase.master.HMaster 대신 org.apache.hadoop.hbase.Server 인스턴스나 org.apache.hadoop.hbase.master.MasterServices 인터페이스 참조를 mock할 수 있어요.
이 예시는 unit.tests의 예시 코드를 기반으로 insertRecord 메서드를 테스트해요.
먼저 Maven POM 파일에 Mockito 의존성을 추가하세요.
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
다음으로 테스트 클래스에 @RunWith 주석을 추가해 Mockito를 사용하도록 지시하세요.
@RunWith(MockitoJUnitRunner.class)
public class TestMyHBaseDAO{
@Mock
Configuration config = HBaseConfiguration.create();
@Mock
Connection connection = ConnectionFactory.createConnection(config);
@Mock
private Table table;
@Captor
private ArgumentCaptor putCaptor;
@Test
public void testInsertRecord() throws Exception {
//return mock table when getTable is called
when(connection.getTable(TableName.valueOf("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");
}
}
이 코드는 HBaseTestObj에 "ROWKEY-1", "DATA-1", "DATA-2" 값을 채워요. 그런 다음 mock된 테이블에 레코드를 삽입해요. DAO가 삽입했을 Put이 포착되고, 값이 기대한 값인지 확인하기 위해 테스트돼요.
여기서 핵심은 Connection과 Table 인스턴스 생성을 DAO 밖에서 관리하는 것이에요. 이렇게 하면 깨끗하게 mock하고 위와 같이 Puts를 테스트할 수 있어요. 마찬가지로 이제 Get, Scan, Delete 같은 다른 연산으로 확장할 수 있어요.
MRUnit
Apache MRUnit은 MapReduce 작업을 단위 테스트할 수 있게 해 주는 라이브러리예요. HBase 작업을 다른 MapReduce 작업과 같은 방식으로 테스트하는 데 사용할 수 있어요.
MyTest라는 HBase 테이블에 쓰는 MapReduce 작업(column family CF 하나)이 주어졌을 때, 그러한 작업의 리듀서는 다음과 같을 수 있어요.
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, let's 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);
}
}
이 코드를 테스트하려면 첫 번째 단계는 Maven POM 파일에 MRUnit에 대한 의존성을 추가하는 것이에요.
<dependency>
<groupId>org.apache.mrunit</groupId>
<artifactId>mrunit</artifactId>
<version>1.0.0 </version>
<scope>test</scope>
</dependency>
다음으로 Reducer 작업에서 MRUnit이 제공하는 ReducerDriver를 사용하세요.
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 );
}
}
MRUnit 테스트는 출력이 기대한 대로인지, HBase에 삽입된 Put이 올바른 값을 가지는지, ColumnFamily와 ColumnQualifier가 올바른 값을 가지는지 확인해요.
MRUnit에는 매핑 작업을 테스트하기 위한 MapperDriver가 포함되며, MRUnit을 사용해 HBase에서 읽기, 데이터 처리, HDFS 쓰기 등 다른 연산을 테스트할 수 있어요.
HBase Mini-Cluster를 사용한 통합 테스트
HBase는 HBaseTestingUtility와 함께 제공되는데, 이를 사용하면 mini-cluster로 통합 테스트를 쉽게 작성할 수 있어요. 첫 번째 단계는 Maven POM 파일에 몇 가지 의존성을 추가하는 것이에요. 버전이 적절한지 확인하세요.
<properties>
<hbase.version>2.0.0-SNAPSHOT</hbase.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-testing-util</artifactId>
<version>${hbase.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
이 코드는 unit.tests에 나온 MyDAO insert에 대한 통합 테스트를 나타내요.
public class MyHBaseIntegrationTest {
private static HBaseTestingUtility utility;
byte[] CF = "CF".getBytes();
byte[] CQ1 = "CQ-1".getBytes();
byte[] CQ2 = "CQ-2".getBytes();
@Before
public void setup() throws Exception {
utility = new HBaseTestingUtility();
utility.startMiniCluster();
}
@Test
public void testInsert() throws Exception {
Table table = utility.createTable(Bytes.toBytes("MyTest"), 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());
}
}
HBase 2.5.0부터는 TestingHBaseCluster를 사용하는 것이 권장돼요.
public class MyHBaseIntegrationTest {
private TestingHBaseCluster cluster;
private Connection conn;
private Admin admin;
private TableName tableName = TableName.valueOf("MyTest");
byte[] CF = "CF".getBytes();
byte[] CQ1 = "CQ-1".getBytes();
byte[] CQ2 = "CQ-2".getBytes();
@Before
public void setUp() throws Exception {
cluster = TestingHBaseCluster.create(TestingHBaseClusterOption.builder().build());
cluster.start();
conn = ConnectionFactory.createConnection(cluster.getConf());
admin = conn.getAdmin();
admin.createTable(TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF)).build());
}
@After
public void tearDown() throws Exception {
admin.close();
conn.close();
cluster.stop();
}
@Test
public void testInsert() throws Exception {
try (Table table = conn.getTable(tableName)) {
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());
}
}
}
이 코드는 HBase mini-cluster를 만들고 시작해요. 다음으로 MyTest라는 테이블과 하나의 column family CF를 만들어요. 레코드가 삽입되고, 같은 테이블에서 Get이 수행되며, 삽입이 확인돼요.
mini-cluster를 시작하는 데 약 20-30초가 걸리지만, 통합 테스트에는 적절할 거예요.
HBaseTestingUtility에 대한 자세한 내용은 HBase Case-Study: Using HBaseTestingUtility for Local Testing and Development (2010) 문서를 참고하세요.