Mavenでマスター/スレーブ構成のMySQLを起動して結合テストをするぞ

この記事はJava Advent Calendarの23日目の記事です。昨日はtoradukaさんのJavaのクラスファイルをjavapとバイナリエディタで読むでした。激しいですねー。明日はsmogamiさんです。

さて、Java EE Advent Calendar18日目の記事の予告通りMavenでまっさらなマスター/スレーブ構成のMySQLクラスタを構築し、そのデータベースに対して結合テストを実行する方法について解説します。サンプルのリポジトリはこちらです。makingチャソがフォークしてSpring Boot版作ってました。Springな方はこちらもぜひ参考にしてみてください!

 jcabi-mysql-maven-plugin

今回のキモはこのjcabi-mysql-maven-pluginです。このプラグインはMavenリポジトリにアップロードされたMySQLをmaven-dependency-pluginでダウンロードして起動するプラグインです。対応している環境は、

  • linux-amd64
  • linux-x86
  • mac-x86_64
  • windows-amd64
  • windows-x86

と一般的な環境は揃っているようです。このようにclassifierを指定することでMavenの実行環境に合ったMySQLを自動的にダウンロードできます。

<plugin>
	<artifactId>maven-dependency-plugin</artifactId>
	<version>2.8</version>
	<executions>
		<execution>
			<phase>pre-integration-test</phase>
			<goals>
				<goal>unpack</goal>
			</goals>
			<configuration>
				<artifactItems>
					<artifactItem>
						<groupId>com.jcabi</groupId>
						<artifactId>mysql-dist</artifactId>
						<version>5.6.14</version>
						<classifier>${mysql.classifier}</classifier>
						<type>zip</type>
						<overWrite>false</overWrite>
						<outputDirectory>${project.build.directory}/mysql-dist</outputDirectory>
					</artifactItem>
				</artifactItems>
			</configuration>
		</execution>
	</executions>
</plugin>

通常MySQLはポート3306で起動しますが、このプラグインはbuild-helper-maven-pluginで予めMySQLが使うポートを予約して使うことを推奨しています。

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>build-helper-maven-plugin</artifactId>
	<version>1.8</version>
	<executions>
		<execution>
			<goals>
				<goal>reserve-network-port</goal>
			</goals>
			<configuration>
				<portNames>
					<portName>mysql.port</portName>
				</portNames>
			</configuration>
		</execution>
	</executions>
</plugin>

こうすることでmvnコマンド実行時に開いているポートが予約され、

<plugin>
	<groupId>com.jcabi</groupId>
	<artifactId>jcabi-mysql-maven-plugin</artifactId>
	<version>0.9</version>
	<executions>
		<execution>
			<id>mysql-test</id>
			<goals>
				<goal>classify</goal>
				<goal>start</goal>
				<goal>stop</goal>
			</goals>
			<configuration>
				<port>${mysql.port}</port>
				<data>${project.build.directory}/mysql-data</data>
			</configuration>
		</execution>
	</executions>
</plugin>

このように待ち受けるポートを指定します。テストなどで使用する場合は

<plugin>
	<artifactId>maven-failsafe-plugin</artifactId>
	<version>2.16</version>
	<executions>
		<execution>
			<goals>
				<goal>integration-test</goal>
				<goal>verify</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<systemPropertyVariables>
			<javax.persistence.jdbc.url>jdbc:mysql:replication://localhost:${mysql.master.port},localhost:${mysql.slave.port}/it</javax.persistence.jdbc.url>
		</systemPropertyVariables>
	</configuration>
</plugin>

このようにシステムプロパティで渡すか、プロパティファイルをMavenのリソース置換で書き換えて渡してもいいでしょう。

このbuild-helper-maven-pluginのポート予約はMySQLにかぎらず結合テストで使うアプリケーションサーバーのポート予約などにも応用できます。ポートが使用中でテストが失敗、といったリスクが無くなるので活用しましょう。

マスター/スレーブ構成にしてみよう

起動させる

build-helper-maven-pluginではマスター用とスレーブ用のポートを予約します。jcabi-mysql-maven-pluginはexecutionを増やし、こちらもマスター用とスレーブ用を定義します。

マスター用

<execution>
	<id>mysql-master</id>
	<goals>
		<goal>classify</goal>
		<goal>start</goal>
		<goal>stop</goal>
	</goals>
	<configuration>
		<port>${mysql.master.port}</port>
		<data>${project.build.directory}/mysql-master</data>
		<options>
			<option>server-id=${mysql.master.port}</option>
			<option>log-bin=mysql-bin</option>
		</options>
	</configuration>
</execution>

スレーブ用

<execution>
	<id>mysql-slave</id>
	<goals>
		<goal>classify</goal>
		<goal>start</goal>
		<goal>stop</goal>
	</goals>
	<configuration>
		<port>${mysql.slave.port}</port>
		<data>${project.build.directory}/mysql-slave</data>
		<options>
			<option>server-id=${mysql.slave.port}</option>
			<option>relay-log=mysql-relay-bin</option>
		</options>
	</configuration>
</execution>

configuration/options/optionでMySQLの起動オプションを渡せます。server-idにはユニークな値を渡す必要があるので予約したポート番号を渡します。レプリケーションの設定としてマスターにはlog-bin=mysql-binを渡しスレーブ側にはrelay-log=mysql-relay-binを渡して起動させます。

レプリケートさせる

さて、MySQL5.5からは起動オプションでmaster-hostやmaster-user、master-passwordを指定してスレーブを起動することができなくなっています。そのためconfiguration/options/optionでこれらのオプションを渡しても上手くいかず泣く泣くchange masterを発行しなければなりません。

そこで今回はsql-maven-pluginを使い、起動したスレーブに対してchange masterを発行します。

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>sql-maven-plugin</artifactId>
	<version>1.5</version>
	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.33</version>
		</dependency>
	</dependencies>
	<configuration>
		<driver>com.mysql.jdbc.Driver</driver>
		<username>root</username>
		<password>root</password>
	</configuration>
	<executions>
		<execution>
			<id>change-master</id>
			<phase>pre-integration-test</phase>
			<goals>
				<goal>execute</goal>
			</goals>
			<configuration>
				<url>jdbc:mysql://localhost:${mysql.slave.port}</url>
				<sqlCommand>
					change master to
						master_host='localhost',
						master_port=${mysql.master.port},
						master_user='root',
						master_password='root';
					start slave;
					show slave status;
				</sqlCommand>
			</configuration>
		</execution>
</plugin>

dependenciesで利用するJDBCドライバを指定し、configurationでドライバのFQCNや接続するユーザー情報を指定します。sqlCommandでchange masterとstart slaveを実行することでレプリケーションを開始しています。

jcabi-mysql-maven-pluginの罠

これでおしまい、晴れてマスター/スレーブ構成のMySQLをMavenで起動できました。となればいいのですが、そうは問屋が卸さないようでして。

実はjcabi-mysql-maven-pluginは起動時にご丁寧にも作成するデータベースに対して–binlog-ignore-dbオプションを付けて起動します。つまりjcabi-mysql-maven-pluginが用意してくれるデータベースはbinlogが出力されずレプリケーションができません。そのためデータベースの作成も独自に行います。

先ほどのsql-maven-pluginでマスターに対してcreate databaseを発行します。

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>sql-maven-plugin</artifactId>
	<version>1.5</version>
.
.
.
	<executions>
		<execution>
			<id>create-database</id>
			<phase>pre-integration-test</phase>
			<goals>
				<goal>execute</goal>
			</goals>
			<configuration>
				<url>jdbc:mysql://localhost:${mysql.master.port}</url>
				<sqlCommand>
					<!-- rootデータベースはbinlog-ignore-dbされているので、テスト用DBを作成 -->
					create database it
				</sqlCommand>
			</configuration>
		</execution>
	</executions>
</plugin>

これで無事itというレプリケーションされるデータベースが作成されました。あとはこちらのエントリの通り煮るなり焼くなり好きにマスター/スレーブ構成のMySQLを弄くり倒せます。

まとめ

いかがでしたでしょうか。いちいちMavenでMySQLとか起動せずに別サーバーにMySQLを起動しておいてそれ使えばいいじゃない、という方も多いかもしれませんが私は極力どのようなテストにおいてもMySQLをはじめとしたミドルウェア込みのテスト環境を提供できるプロジェクトのポータビリティを重視しています。(Arquillianの結合テストも基本的にEmbedded派です)

Mavenだけでどのような環境でもすぐにテストを実行できると、開発チームへ新たにメンバーが追加された際などにいちいち接続用アカウントの作成やデータベースの作成、テストデータの流し込みなどといった煩雑な作業をすることがなくなり、やれアカウントが分かりませんやら、テストデータが足りないといった対応などのストレスから開放されることがとても大きいと感じています。

ミドルウェアのログファイルもローカルで読み込めるので、MySQLで言えば正しくインデックスが使われているか、クエリの内容は問題ないかなどの確認やテストを行うこともできます。統合的に”固い”テストを行う方法の一つとしてこういったアプローチもぜひ頭の片隅に入れておいてください。

とは言え、ぶっちゃけプラグインをこねくり回さずに、maven-docker-pluginとかを使ってDockerでMySQLクラスタを用意してMaven実行時に環境構築して使う今風ベストプラクティスなんじゃないでしょうか。(投げやり)

「Mavenでマスター/スレーブ構成のMySQLを起動して結合テストをするぞ」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>