Spring REST Docs 를 사용하여 API 명세서 작성 자동화하기

jjunhwan.kim·2023년 10월 9일
0

스프링

목록 보기
3/10
post-thumbnail
post-custom-banner

개요

  • 서버 애플리케이션에서 HTTP API를 제공할 때 API 명세서를 작성하게 됩니다.
  • API가 변경 될 때마다 API 명세서도 같이 업데이트 해야합니다.
    API 명세서를 직접 작성하는 경우 코드의 변경 이력과 명세서의 변경 이력을 같이 관리하고 서로 동기화해야합니다.
  • Spring REST Docs를 사용하면 API 명세서 작성을 자동화 할 수 있습니다.
  • API 테스트 코드를 작성하고 테스트가 통과하면 API 명세서가 생성됩니다.
  • 따라서 API가 변경되어도 테스트 코드를 통과해야 하므로 코드와 API 명세서의 불일치가 발생하지 않습니다.
  • 공식 문서 https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle 참고 바랍니다.

프로젝트 생성

먼저 스프링 프로젝트를 생성합니다. https://start.spring.io 에서 아래와 같이 Spring Web, Lombok 의존성을 추가하여 생성합니다.
이번 예제에서는 API만 구현하므로 데이터베이스 관련 의존성은 추가하지 않았습니다.

REST Docs 의존성 추가

build.gradle 파일에 Spring REST Docs 의존성을 추가합니다. 아래 주석의 1 ~ 8번 까지의 내용을 추가합니다.

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.4'
	id 'io.spring.dependency-management' version '1.1.3'
	id 'org.asciidoctor.jvm.convert' version '3.3.2' // (1)
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
	asciidoctorExt // (2)
}

repositories {
	mavenCentral()
}

// (3)
ext {
	set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' // (4)
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' // (5)
}

tasks.named('test') {
	outputs.dir snippetsDir // (6)
	useJUnitPlatform()
}

// (7)
tasks.named('asciidoctor') {
	inputs.dir snippetsDir
	configurations 'asciidoctorExt'
	sources{
		include("**/index.adoc")
	}
	baseDirFollowsSourceFile()
	dependsOn test
}

// (8)
bootJar {
	dependsOn asciidoctor
	from("${asciidoctor.outputDir}") {
		into 'static/docs'
	}
}
  • (1): asciidoctor 플러그인을 추가합니다.
  • (8): bootJar 태스크를 실행하면 static/docs 경로에 API 명세서 문서가 포함됩니다.

API 작성

게시글 생성, 조회, 업데이트, 삭제 API를 작성해보겠습니다.

먼저 API 공통 응답 포맷을 사용하기 위해 아래와 같이 CommonResponse 제네릭 클래스를 작성합니다.

게시글 목록 조회, 게시글 조회, 게시글 생성, 게시글 업데이트, 게시글 삭제 API를 아래와 같이 작성합니다.

PostService 클래스에는 비즈니스 로직을 작성합니다. 저는 일단 null만 리턴하도록 구현하였습니다. 테스트시에는 Mock 객체를 삽입할 것이기 때문입니다.

API 테스트 코드 작성

먼저 아래와 같이 docs 패키지를 생성합니다.

RestDocsTest 클래스를 작성합니다. 이 클래스는 추상 클래스로 템플릿 메서드 패턴을 사용하여 initializeController 메서드를 하위 클래스에서 구현하도록 합니다.

다음으로 docs/post 패키지를 생성하고 RestDocsTest 클래스를 상속 받는 PostControllerDocsTest 클래스를 작성합니다. PostService Mock 객체를 생성하고, initializeController 메서드를 아래와 같이 구현합니다.

게시글 목록 조회 API 테스트 코드입니다.

게시글 조회 API 테스트 코드입니다.

게시글 생성 API 테스트 코드입니다.

게시글 업데이트 API 테스트 코드입니다.

게시글 삭제 API 테스트 코드입니다.

API 문서 작성

src 밑에 docs/index.adoc 파일을 생성합니다.

index.adoc 파일은 API 문서 자동 생성시 index.html 파일로 변환되어 jar 파일에 포함됩니다. 아래와 같이 작성합니다.

index.adoc 파일에는 API 문서의 전체 구조를 정의합니다. 그리고 다른 adoc 파일을 incldue 명령어를 사용하여 삽입할 수 있습니다. 아래에서는 api/post/post.adoc 파일을 삽입하였습니다.

ifndef::snippets[]
:snippets: ../../build/generated-snippets
endif::[]
= API 명세서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:

= Common

== 공통 API 응답 포맷

== 응답 상태 코드 및 메세지


= API

== Post API
include::api/post/post.adoc[]

이어서 docs/api/post/post.adoc 파일을 생성합니다. 이 파일에는 게시글 생성, 조회, 업데이트, 삭제 API를 작성합니다. 전체적인 구조만 작성하고, 실제 내용은 자동 생성된 adoc 파일 들을 include 명령어를 통해 삽입합니다.

=== 게시글 목록 조회 API

==== 1. Curl Request
include::{snippets}/get-posts/curl-request.adoc[]

==== 2. HTTP Request
include::{snippets}/get-posts/http-request.adoc[]

==== 3. HTTP Request Headers
// include::{snippets}/get-posts/request-headers.adoc[]

==== 4. HTTP Request Parameters

===== 1) Path Parameters
// include::{snippets}/get-posts/path-parameters.adoc[]

===== 2) Query Parameters
include::{snippets}/get-posts/query-parameters.adoc[]

===== 3) Form Parameters
// include::{snippets}/get-posts/form-parameters.adoc[]

==== 5. HTTP Request Body
include::{snippets}/get-posts/request-body.adoc[]
// include::{snippets}/get-posts/request-fields.adoc[]

==== 6. HTTP Response Body
include::{snippets}/get-posts/response-body.adoc[]
include::{snippets}/get-posts/response-fields.adoc[]

=== 게시글 조회 API

==== 1. Curl Request
include::{snippets}/get-post/curl-request.adoc[]

==== 2. HTTP Request
include::{snippets}/get-post/http-request.adoc[]

==== 3. HTTP Request Headers
// include::{snippets}/get-post/request-headers.adoc[]

==== 4. HTTP Request Parameters

===== 1) Path Parameters
include::{snippets}/get-post/path-parameters.adoc[]

===== 2) Query Parameters
// include::{snippets}/get-post/query-parameters.adoc[]

===== 3) Form Parameters
// include::{snippets}/get-post/form-parameters.adoc[]

==== 5. HTTP Request Body
include::{snippets}/get-post/request-body.adoc[]
// include::{snippets}/get-post/request-fields.adoc[]

==== 6. HTTP Response Body
include::{snippets}/get-post/response-body.adoc[]
include::{snippets}/get-post/response-fields.adoc[]

=== 게시글 생성 API

==== 1. Curl Request
include::{snippets}/create-posts/curl-request.adoc[]

==== 2. HTTP Request
include::{snippets}/create-posts/http-request.adoc[]

==== 3. HTTP Request Headers
include::{snippets}/create-posts/request-headers.adoc[]

==== 4. HTTP Request Parameters

===== 1) Path Parameters
//include::{snippets}/create-posts/path-parameters.adoc[]

===== 2) Query Parameters
//include::{snippets}/create-posts/query-parameters.adoc[]

===== 3) Form Parameters
// include::{snippets}/create-posts/form-parameters.adoc[]

==== 5. HTTP Request Body
include::{snippets}/create-posts/request-body.adoc[]
// include::{snippets}/create-posts/request-fields.adoc[]

==== 6. HTTP Response Body
include::{snippets}/create-posts/response-body.adoc[]
include::{snippets}/create-posts/response-fields.adoc[]

=== 게시글 업데이트 API

==== 1. Curl Request
include::{snippets}/update-posts/curl-request.adoc[]

==== 2. HTTP Request
include::{snippets}/update-posts/http-request.adoc[]

==== 3. HTTP Request Headers
include::{snippets}/update-posts/request-headers.adoc[]

==== 4. HTTP Request Parameters

===== 1) Path Parameters
include::{snippets}/update-posts/path-parameters.adoc[]

===== 2) Query Parameters
// include::{snippets}/update-posts/query-parameters.adoc[]

===== 3) Form Parameters
// include::{snippets}/update-posts/form-parameters.adoc[]

==== 5. HTTP Request Body
include::{snippets}/update-posts/request-body.adoc[]
include::{snippets}/update-posts/request-fields.adoc[]

==== 6. HTTP Response Body
include::{snippets}/update-posts/response-body.adoc[]
include::{snippets}/update-posts/response-fields.adoc[]

=== 게시글 삭제 API

==== 1. Curl Request
include::{snippets}/delete-posts/curl-request.adoc[]

==== 2. HTTP Request
include::{snippets}/delete-posts/http-request.adoc[]

==== 3. HTTP Request Headers
include::{snippets}/delete-posts/request-headers.adoc[]

==== 4. HTTP Request Parameters

===== 1) Path Parameters
include::{snippets}/delete-posts/path-parameters.adoc[]

===== 2) Query Parameters
// include::{snippets}/delete-posts/query-parameters.adoc[]

===== 3) Form Parameters
// include::{snippets}/delete-posts/form-parameters.adoc[]

==== 5. HTTP Request Body
include::{snippets}/delete-posts/request-body.adoc[]
// include::{snippets}/delete-posts/request-fields.adoc[]

==== 6. HTTP Response Body
include::{snippets}/delete-posts/response-body.adoc[]
include::{snippets}/delete-posts/response-fields.adoc[]

API 문서 생성

위에서 작성한 API 문서에서 각 API에 삽입할 adoc 문서 들을 테스트 코드를 기반으로 자동 생성합니다.

당연히 테스트 코드가 통과되어야 문서가 생성됩니다.

터미널에서 Gradle의 bootJar 태스크를 실행합니다.

./gradlew bootJar

태스크 실행이 성공하면, build/generated-snippets 디렉토리 하위에 위의 테스트 코드에서 document 메서드에 전달하는 파라미터 값으로 디렉토리가 생성됩니다.
해당 디렉토리에는 테스트 코드를 기반으로 adoc 확장자의 문서가 생성됩니다.

각각의 문서에는 CURL 명령어, HTTP 요청, HTTP 응답, HTTP 헤더, 파라미터, 바디 등의 정보가 테스트 코드에서 작성한 내용을 기반으로 생성됩니다.

API 문서 확인

bootJar 태스크로 생성된 jar 파일에 API 문서가 포함됩니다. 따라서 애플리케이션을 먼저 실행합니다.

java -jar build/libs/restdocs-0.0.1-SNAPSHOT.jar

http://localhost:8080/docs/index.html 경로로 접속하면 API 문서가 출력됩니다.

예제 프로젝트

전체 코드는 https://github.com/nefertirii/restdocs 에서 확인하실 수 있습니다.

post-custom-banner

0개의 댓글