Spring REST Docsでドキュメント作成

# はじめに
この記事は リクルートライフスタイル Advent Calendar 2015
[リクルートライフスタイル Advent Calendar 2015 - Qiita](http://qiita.com/advent-calendar/2015/recruitlifestyle) の9日目です。
ホットペッパービューティーで開発を担当している満澤です。

現在、Java 8 + Spring Boot 1.3.0 + MyBatis 3.2.8でRestfulな新規APIを開発しています。

Spring Bootは、小・中規模な新規サービスを高いスピード感で、
開発していくのに適しているんじゃないかと感じています。
現実、まわりでもそういった案件がどんどん増えてます。

その際、当然、新たにドキュメントの整備をしていかなければならないのですが、
ここで、最近新しい技術をSpringが提供し始めました。
**その名もSpring REST Docs。**

何ができるかというと、
Spring MVC Test を実行した結果をスニペット(Asciidoc)として出力できます。
自分で手書きしたドキュメント(Asciidoc)とスニペットを組み合わせられます。

さっそく、どんなドキュメントが出力されるか試してみます。

# 環境
- Java 1.8.0_45
- Maven 3.2.1
- Spring Boot 1.3.0
- Spring REST Docs 1.0.0

# サンプル
Spring BootのREST APIにREST Docsでドキュメント生成するサンプルです。
※Spring Bootの実装については色んなところで記事がでているので割愛

```xml:pom.xml
	<!-- REST Docsに関する記載を抜粋 -->

	<properties>
		<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
	</properties>

	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-surefire-plugin</artifactId>
		<configuration>
			<includes>
				<include>**/*Documentation.java</include>
			</includes>
		</configuration>
	</plugin>
	<plugin>
		<groupId>org.asciidoctor</groupId>
		<artifactId>asciidoctor-maven-plugin</artifactId>
		<version>1.5.2</version>
		<executions>
			<execution>
				<id>generate-docs</id>
				<phase>package</phase>
				<goals>
					<goal>process-asciidoc</goal>
				</goals>
				<configuration>
					<backend>html</backend>
					<doctype>book</doctype>
					<attributes>
						<snippets>${snippetsDirectory}</snippets>
					</attributes>
				</configuration>
			</execution>
		</executions>
	</plugin>
	<plugin>
		<artifactId>maven-resources-plugin</artifactId>
		<version>2.7</version>
		<executions>
			<execution>
				<id>copy-resources</id>
				<phase>prepare-package</phase>
				<goals>
					<goal>copy-resources</goal>
				</goals>
				<configuration>
					<outputDirectory>
						${project.build.outputDirectory}/static/docs
					</outputDirectory>
					<resources>
						<resource>
							<directory>
								${project.build.directory}/generated-docs
							</directory>
						</resource>
					</resources>
				</configuration>
			</execution>
		</executions>
	</plugin>

	<dependency>
		<groupId>org.springframework.restdocs</groupId>
		<artifactId>spring-restdocs-mockmvc</artifactId>
		<version>1.0.0.RELEASE</version>
		<scope>test</scope>
	</dependency>
```

```java:RestDocsExampleTest.java

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ApplicationContext.class)
@WebAppConfiguration
public class RestDocsExampleTest {

	@Rule
	public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");

	@Autowired
	private WebApplicationContext context;

	private MockMvc mockMvc;

	@Before
	public void setUp() {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
				.apply(documentationConfiguration(this.restDocumentation)).build();
	}

	@Test
	public void testRequestMapping() throws Exception {
		this.mockMvc.perform(get("/api/v1/employee/{year}?employeId=E000000001", "2015")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andDo(document("controller-doc",
						pathParameters(
								parameterWithName("year").description("入社年：必須、yyyy形式")),
						requestParameters(
								parameterWithName("employeId").description("従業員ID：任意、半角英数字10桁"))
				));
	}
```


ポイントはMockMvc.andDo(document("controller-doc", ・・・)でドキュメントが出力されるところ、
pathParameters()、requestParameters()でドキュメントに出力する内容が記載できます。
上記の例ではコントローラクラスをテスト対象にして、
URLパラメータについて、ドキュメントに出力するようにしていますが、
これ以外にも、レスポンスやヘッダの中身など様々な記載が可能です。

出力されたドキュメント
![doc.jpg](https://qiita-image-store.s3.amazonaws.com/0/102792/1919ca75-18b0-b669-e8d7-bbd697f649f9.jpeg)


# まとめ
簡単です、とてもシンプル。
ドキュメントの質も、一定のラインは満たせているでしょうし、(Asciidocなので、リッチにカスタマイズも可能)
**何より、ドキュメント作成のコストをテストコードにかけれる**という仕組みがとても好印象をもちました。
ドキュメントが置いてけぼりというのはよく聞く話ですが、
テストが成功した時にドキュメントが生成されるので、最新の状態を保ちやすい、
CIと組み合わせて色んなことできるでしょうし、Jarなどに含めて一緒に配布することも容易です。
実際のプロダクトに適用させるには、詰めるところはありそうですが、
Springで開発しているのであれば、導入を検討してもいいのではないでしょうか。

参考
http://docs.spring.io/spring-restdocs/docs/1.0.0.RELEASE/reference/html5/