Java EE でテストやるなら Arquillian
Oracle の強力なプッシュもあってか、Java EE が勢いを盛り返しつつある昨今ですが、Java EE にはテスティングフレームワークはありません。やっぱり JUnit とか使います。
しかし、Java EE はアプリケーションサーバーによるコンテナ管理を中心に動作するので、単純に new でオブジェクトを生成して試すことはできず、JUnit だけでは力不足。そこで登場するのが Arquillian。
Arquillian is an innovative and highly extensible testing platform for the JVM that enables developers to easily create automated integration, functional and acceptance tests for Java middleware.
Arquillian は JBoss Community で開発されている Java EE 結合テスト実行プラットフォーム。これ使えばモックなしでテストができるらしい。
試行環境
- Windows 8.1
- WildFly 8.2
- Lombok
- H2 Database 1.3
- Eclipse Luna
- Gradle 2.1
ソースコード
割と最小構成で作った。
ビルドスクリプト
- テストを行うので javax:javaee-api じゃなくて、実装の付いてる org.jboss.spec:jboss-javaee-7.0 を指定。
apply plugin: 'war'
def javaVersion = 1.8
def defaultEncoding = 'UTF-8'
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
tasks.withType(AbstractCompile) each { it.options.encoding = defaultEncoding }
webAppDirName = 'WebContent'
repositories {
mavenCentral()
}
dependencies {
providedCompile 'org.projectlombok:lombok:1.16.2'
providedCompile 'org.jboss.spec:jboss-javaee-7.0:1.0.2.Final'
}
JPA
クラス
- Lombok 使ってます。
@SuppressWarnings("serial")
@Data
@Entity
@NamedQuery(name = Country.JPQL_SELECE_ALL, query = "SELECT c FROM Country c")
public class Country implements Serializable {
public static final String JPQL_SELECE_ALL = "Country.selectALL";
@Id @GeneratedValue
private long id;
private String name;
private int population;
}
@RequestScoped
public class CountryRepository {
@PersistenceContext
private EntityManager em;
@Transactional
public List<Country> getAllCountries() {
return em.createNamedQuery(Country.JPQL_SELECE_ALL, Country.class).getResultList();
}
}
設定ファイル
- ファイルを使って WildFly にデータソースを登録。
<?xml version="1.0" encoding="UTF-8"?>
<datasources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ironjacamar.org/doc/schema"
xsi:schemaLocation="http://www.ironjacamar.org/doc/schema http://www.ironjacamar.org/doc/schema/datasources_1_1.xsd">
<datasource jndi-name="java:global/jdbc/example" pool-name="ExampleDS">
<connection-url>jdbc:h2:mem:example;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
</datasource>
</datasources>
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="sample-web-project">
<jta-data-source>java:global/jdbc/example</jta-data-source>
<class>entity.Country</class>
<properties>
<property name="javax.persistence.database-product-name"
value="H2" />
<property name="javax.persistence.database-major-version"
value="1" />
<property name="javax.persistence.database-minor-version"
value="3" />
</properties>
</persistence-unit>
</persistence>
JAX-RS
@ApplicationPath("api")
public class ApplicationConfig extends Application {
}
@Path("country")
@RequestScoped
public class CountryResource {
@Inject
private CountryRepository repository;
@GET
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@Transactional
public List<Country> getAllCountries() {
return repository.getAllCountries();
}
}
テストコード
ビルドスクリプト
- Arquillian の依存性を取得するために、JBoss の Maven リポジトリを追加する。
- jboss-javaee-7.0 は providedCompile で指定されており、@RunAsClient が付与されアプリケーションサーバー上で実行されない JAX-RS のテストコードは依存性不足に陥ってしまうため、testRuntime として別途依存性を追加しておく。
...
repositories {
maven { url 'http://repository.jboss.org/nexus/content/groups/public' }
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.jboss.arquillian.junit:arquillian-junit-container:1.1.7.Final'
testCompile 'org.wildfly:wildfly-arquillian-container-remote:8.2.0.Final'
testRuntime 'org.jboss.resteasy:resteasy-client:3.0.10.Final'
testRuntime 'org.jboss.resteasy:resteasy-jackson-provider:3.0.10.Final'
}
JPA
設定ファイル
- テスト用には別個のデータソース登録ファイルを作成した。
<?xml version="1.0" encoding="UTF-8"?>
<datasources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ironjacamar.org/doc/schema"
xsi:schemaLocation="http://www.ironjacamar.org/doc/schema http://www.ironjacamar.org/doc/schema/datasources_1_1.xsd">
<datasource jndi-name="java:global/jdbc/exampleTest" pool-name="ExampleDS">
<connection-url>jdbc:h2:mem:example-test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
</datasource>
</datasources>
- persistence.xml も別個に用意。
- AP サーバー起動時に自動的にスキーマを作成し、データ登録スクリプトを実行する。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="sample-web-project">
<jta-data-source>java:global/jdbc/exampleTest</jta-data-source>
<class>entity.Country</class>
<properties>
<property name="javax.persistence.schema-generation.database.action"
value="drop-and-create" />
<property name="javax.persistence.schema-generation.create-source"
value="metadata" />
<property name="javax.persistence.schema-generation.drop-source"
value="metadata" />
<property name="javax.persistence.sql-load-script-source"
value="insert-initial-data.sql" />
<property name="javax.persistence.database-product-name"
value="H2" />
<property name="javax.persistence.database-major-version"
value="1" />
<property name="javax.persistence.database-minor-version"
value="3" />
</properties>
</persistence-unit>
</persistence>
INSERT INTO Country SELECT * FROM CSVREAD('classpath:countries.csv', null, 'charset=UTF-8');
COMMIT;
id,name,population
1,Japan,127300000
2,USA,318900000
3,China,1357000000
テストクラス
- 別個に用意した設定ファイルを WAR に含める。
@RunWith(Arquillian.class)
public class CountryRepositoryTest {
@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(Country.class, CountryRepository.class)
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsResource("insert-initial-data.sql")
.addAsResource("countries.csv")
.addAsWebInfResource("test-h2-ds.xml");
}
@Inject
private CountryRepository repository;
@Test
public void getAll() {
assertThat(repository.getAll().size(), is(3));
}
}
JAX-RS
- JPA のテスト同様、別個に用意した設定ファイルを WAR に含める。
-
@ArquillianResource
アノテーションでアプリケーションコンテキストを含む URL を取得できる。 - Web API へのアクセスは、AP サーバー外で実行したいので
@RunAsClient
を付与する。
@RunWith(Arquillian.class)
public class CountryResourceTest {
@ArquillianResource
private URL baseUrl;
private String resourcePrefix = ApplicationConfig.class.getAnnotation(ApplicationPath.class)
.value();
@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(Country.class, CountryRepository.class, ApplicationConfig.class,
CountryResource.class)
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsResource("insert-initial-data.sql")
.addAsResource("countries.csv")
.addAsWebInfResource("test-h2-ds.xml");
}
@Test
@RunAsClient
public void test() throws Exception {
List<Country> list = ClientBuilder.newClient()
.target(new URL(baseUrl, resourcePrefix + "/country").toURI())
.request(MediaType.APPLICATION_JSON)
.get(new GenericType<List<Country>>() {});
assertThat(list.size(), is(3));
}
}
Arquillian 設定ファイル
<?xml version="1.0"?>
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian http://www.jboss.org/schema/arquillian/arquillian_1_0.xsd">
<defaultProtocol type="Servlet 3.0" />
<container qualifier="arquillian-wildfly-remote" default="true" />
</arquillian>
テストカバレッジ取得
ビルドスクリプト
- AP サーバー上でのカバレッジを取得するために arquillian-jacoco を依存性として追加。
- arquillian-jacoco には org.jacoco.core が必要となるのでこれも追加。
...
def jacocoVersion = '0.7.2.201409121644'
dependencies {
testCompile 'org.jboss.arquillian.extension:arquillian-jacoco:1.0.0.Alpha7'
testCompile "org.jacoco:org.jacoco.core:${jacocoVersion}"
}
jacoco.toolVersion = jacocoVersion
テスト実行
gradle test jacocoTestReport
を実行。
できた!
でも、残念ながら@RunAsClient
で実行した JAX-RS のテストは AP サーバー外で実行されているのでカバレッジが取得できていない模様。対処策の情報を探したけれど見つからなかった。無念。
感じたこと
- ビルドツール使わない限りは Arquillian を使うのはちょっと厳しい。使い方覚えたほうがいい。
- Java の諸ツール、Gradle への対応が進んでいるけれど、まだまだ Maven が中心になっていることが多い。
- 特に Arquillian は Maven の使用が前提になっているなあと感じた。
- Arquillian の Gradle プラグインもあるんだけど、あんまりメンテされてなくて WildFly に対応していない…。