モチベーション
Hive のクエリのテストしたい。
mysql だったら、docker-compose とか比較的簡単にテストできるけど、hiveのクエリのテストも同じようにしたいけど、どうしたらいいですかね。(docker で ゴニョゴニョするのはちょっと辛そう..)
ってわけで良さそうなプラグインを適当に選んで試してみました。
選んだのは、公式でいくつか紹介されているツールの中で git のスターが一番多かった HiveRunner です。(バージョンは 4.1.0
)
準備
HiveRunner
は基本的に Junit
のテストとして、クエリのテストをします。
外部の依存関係は必要なく、JVM
上にHiveServer
を立てて、Junit
がそのHiveServer
にhive sql
を実行するイメージみたいです。
今回は maven
でプロジェクトを作っていきます。
以下、今回実行する際の pom
です。 特に理由はありませんが、BEELINE
のエミュレータを指定しています。
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<forkMode>always</forkMode>
<systemProperties>
<!-- Defaults to HIVE_CLI, other options include BEELINE and HIVE_CLI_PRE_V200 -->
<commandShellEmulator>BEELINE</commandShellEmulator>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.klarna</groupId>
<artifactId>hiverunner</artifactId>
<version>4.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
とりあえず実行してみる
@RunWith(StandaloneHiveRunner.class)
public class TestHiveRunner {
@HiveSQL(files = {})
private HiveShell shell;
/*
* DB と テーブルを作成
*/
@Before
public void setupSourceDatabase() {
shell.execute(
"CREATE DATABASE source_db; " +
"CREATE TABLE source_db.test_table (year STRING, value INT);");
shell.execute(
"CREATE DATABASE my_schema; " +
"CREATE EXTERNAL TABLE my_schema.result " +
"(year STRING, value INT) " +
"STORED AS PARQUET " +
"TBLPROPERTIES (\"parquet.compress\"=\"snappy\")");
}
@Test
public void testMaxValueByYear() {
/*
* 集計対象のテーブルにテストデータを格納
*/
shell.insertInto("source_db", "test_table")
.withColumns("year", "value")
.addRow("2014", 3)
.addRow("2014", 4)
.addRow("2015", 2)
.addRow("2015", 5)
.commit();
/*
* 集計クエリを実行 (INSERT クエリ)
*/
shell.executeStatement("INSERT INTO my_schema.result " +
"SELECT " +
" year, " +
" MAX(value) " +
"FROM " +
" source_db.test_table " +
"GROUP BY year");
/*
* 集計結果が INSERT されているテーブルから結果を取得
*/
List<Object[]> result = shell.executeStatement("SELECT * FROM my_schema.result");
assertEquals(2, result.size());
assertArrayEquals(new Object[]{"2014",4}, result.get(0));
assertArrayEquals(new Object[]{"2015",5}, result.get(1));
}
}
上記ほとんど Example に載っているコードをちょっと書き換えたものです。
とても簡単にテストすることができるように見えます。
切り出して置いたクエリの読み込みや、テストデータのインサートが多くなってきて別途tsv
等に切り出した場合など、それらを読み込むこともできます。
それぞれちょっとだけ見ていく
@HiveSQL
// デフォルトで src/test/resources ディレクトリを参照している。
@HiveSQL(files = {"create_test_table.sql", "create_max.sql"})
private HiveShell shell;
files = {}
にSQLファイルを指定することで、インスタンスが作られた後に自動で実行してくれる。
@HiveSQL(files = {...}, autoStart = false)
とすることで、任意のセットアップを行なった後、起動させることができる。(start()メソッドをコールする)
ちなみに、任意のセットアップで設定出来る項目は以下のようなものがあります。(オーバーロードされているメソッド多々あり.)
// HiveConf を設定
void setProperty(String key, String value);
void setHiveConfValue(String key, String value);
// テストデータを HDFS にコピー
void addResource(String targetFile, File sourceFile);
// HiveShell起動時に実行されるスクリプトの登録
// @HiveSetupScript でも同様だが、以下はスクリプトの実行順序が保証される。
void addSetupScript(String script);
void addSetupScripts(Charset charset, File... scripts);
// stream を開いて、テストデータを HDFS に書き込んでくれるらしい
OutputStream getResourceOutputStream(String targetFile);
execute
// 直書き
shell.execute("CREATE DATABASE source_db; " +
"CREATE TABLE source_db.test_table (year STRING, value INT);");
// 切り出されたSQLの読み込みも可
shell.execute(Paths.get("src/test/resources/calculate_max.sql"));
返り値なしの、スクリプト(クエリ)実行。
;
で区切ることで、複数のクエリを実行できる。
複数のクエリを実行できるから、返り値がないってことですかね。
executeQuery & executeStatement
// 下記、executeQuery でも同様に実行できる。
shell.executeStatement("INSERT INTO my_schema.result " +
"SELECT " +
" year, " +
" MAX(value) " +
"FROM " +
" source_db.test_table " +
"GROUP BY year");
// executeQuery に限り、切り出されたSQLの読み込みも可
shell.execute(Paths.get("src/test/resources/calculate_max.sql"));
execute
と異なり、複数のクエリを一回で実行することはできないし、文末に ;
が含まれるとエラーになる。
その代わり、List<String>
でクエリの実行結果が返ってくる。
insertInto
shell.insertInto("source_db", "test_table")
.withColumns("year", "value")
.addRow("2014", 3)
.addRow("2014", 4)
.addRow("2015", 2)
.addRow("2015", 5)
.commit();
// tsv 等からデータをインサートすることもできる。
shell.insertInto("source_db", "test_table")
.withColumns("year", "value")
.addRowsFromTsv(new File("src/test/resources/insert_data_of_test_table.tsv"))
.commit();
このメソッドは、commit()
しないと実行されない。
テストデータの準備はめんどくさいですが、tsv
でインサートしてくれるのはとても便利ですね。
まとめ
クエリのテストやちょっとクエリ試したいときなど、(ちょっと重い感じがしますが)使えそうですね。
一方で、カラム数が多いテーブルだったり、テストデータの量が多い場合のテストだったり、めっちゃ複雑なクエリのテスト等では、メモリや実行速度的に辛そうですが、どうなるのでしょうか (READMEに対処法など書いてありますが..)
とはいえ、pom
に hiverunner
を入れるだけで、hive
の sql
のテストができるのは魅力的ですね。