JPA & JAX-RS のテストを Arquillian を使ってやってみた（テストカバレッジ取得のおまけつき）

# Java EE でテストやるなら Arquillian
Oracle の強力なプッシュもあってか、Java EE が勢いを盛り返しつつある昨今ですが、Java EE にはテスティングフレームワークはありません。やっぱり JUnit とか使います。

しかし、Java EE はアプリケーションサーバーによるコンテナ管理を中心に動作するので、単純に new でオブジェクトを生成して試すことはできず、JUnit だけでは力不足。そこで登場するのが **Arquillian**。

![arquillian_logo_450px.png](https://qiita-image-store.s3.amazonaws.com/0/43205/42654daa-6cd1-10b4-6a5c-0fe6ac7c2234.png)


[Arquillian &#183; Write Real Tests](http://arquillian.org/)

> 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 を指定。

```groovy:build.gradle
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 使ってます。

```Country.java
@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;

}
```

```CountryRepository.java
@RequestScoped
public class CountryRepository {

  @PersistenceContext
  private EntityManager em;

  @Transactional
  public List<Country> getAllCountries() {
    return em.createNamedQuery(Country.JPQL_SELECE_ALL, Country.class).getResultList();
  }

}
```

### 設定ファイル
- ファイルを使って WildFly にデータソースを登録。

```WEB-INF/h2-ds.xml
<?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>
```

```META-INF/persistence.xml
<?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
```ApplicationConfig.java
@ApplicationPath("api")
public class ApplicationConfig extends Application {
}
```

```CountryResource.java
@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 として別途依存性を追加しておく。

```groovy:build.gradle
...

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
### 設定ファイル
- テスト用には別個のデータソース登録ファイルを作成した。

```test-h2-ds.xml
<?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 サーバー起動時に自動的にスキーマを作成し、データ登録スクリプトを実行する。

```test-persistence.xml
<?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-initial-data.sql
INSERT INTO Country SELECT * FROM CSVREAD('classpath:countries.csv', null, 'charset=UTF-8');
COMMIT;
```

```countries.csv
id,name,population
1,Japan,127300000
2,USA,318900000
3,China,1357000000
```

### テストクラス
- 別個に用意した設定ファイルを WAR に含める。

```CountryRepositoryTest.java
@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`を付与する。

```CountryResourceTest.java
@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 設定ファイル
```arquillian.xml
<?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 が必要となるのでこれも追加。

```groovy:build.gradle
...

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`を実行。

**テスト**
![tests.PNG](https://qiita-image-store.s3.amazonaws.com/0/43205/122ba1b0-33dd-5573-2e66-64cac1ac41fa.png)

**テストカバレッジ**
![jacoco.PNG](https://qiita-image-store.s3.amazonaws.com/0/43205/989f9191-fc3c-e92d-9c48-0ae854168848.png)

できた！

でも、残念ながら`@RunAsClient`で実行した JAX-RS のテストは AP サーバー外で実行されているのでカバレッジが取得できていない模様。対処策の情報を探したけれど見つからなかった。無念。

# 感じたこと
- ビルドツール使わない限りは Arquillian を使うのはちょっと厳しい。使い方覚えたほうがいい。
- Java の諸ツール、Gradle への対応が進んでいるけれど、まだまだ Maven が中心になっていることが多い。
  - 特に Arquillian は Maven の使用が前提になっているなあと感じた。
  - Arquillian の Gradle プラグインもあるんだけど、あんまりメンテされてなくて WildFly に対応していない…。

# 参考サイト
- [MavenではじめるJava EE - nekop&#39;s blog](http://nekop.hatenablog.com/entry/20131202/1385965701)
- [Arquillianを使った本物のJava EEテスト - nekop&#39;s blog](http://nekop.hatenablog.com/entry/20111217/1324273860)
- [結合テストとワンクリックデプロイがすぐに使えるJava EE 7開発のためのbuild.gradle - Qiita](http://qiita.com/tq_jappy/items/5bce17b81c948e78d486)