Swagger | Spring Rest Docs |
---|---|
애터네이션 기반의 API 문서화 방식 | 테스트 코드 기반의 API 문서화 방식 |
애플리케이션 코드에 문서화를 위한 애너테이션들이 포함된다. | 애플리케이션 코드에 문서화를 위한 정보들이 포함되지 않는다. |
가독성 및 유지 보수성이 떨어진다. | 테스트 케이스의 실행이 “passed”여야 API 문서가 생성된다. |
API 문서와 API 코드 간의 정보 불일치 문제가 발생할 수 있다. | 테스트 케이스를 반드시 작성해야된다. |
API 툴로써의 기능을 활용할 수 있다. | API 툴로써의 기능은 제공하지 않는다. |
passed
”이면 다음 작업을 진행하고, “failed
”이면 문제를 해결하기 위해 테스트 케이스를 수정한 후, 다시 테스트를 진행해야 한다.passed
”이면 테스트 코드에 포함된 API 스펙 정보 코드를 기반으로 API 문서 스니핏이 .adoc
확장자를 가진 파일로 생성됩니다.스니핏(snippet)은 일반적으로 코드의 일부 조각을 의미
테스트 케이스 하나 당 하나의 스니핏이 생성되며, 여러개의 스니핏을 모아서 하나의 API 문서를 생성
//.adoc 파일 확장자를 가지는 AsciiDoc 문서를 생성해주는 Asciidoctor를 사용하기 위한 플러그인을 추가
plugins {
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
//ext 변수의 set() 메서드를 이용해서 API 문서 스니핏이 생성될 경로를 지정
ext {
set('snippetsDir', file("build/generated-snippets"))
}
// AsciiDoctor에서 사용되는 의존 그룹을 지정
configurations {
asciidoctorExtensions
}
dependencies {
//spring-restdocs-core와 spring-restdocs-mockmvc 의존 라이브러리가 추가
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
//spring-restdocs-asciidoctor 의존 라이브러리를 추가
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
}
// :test task 실행 시, API 문서 생성 스니핏 디렉토리 경로를 설정
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
//:asciidoctor task 실행 시, Asciidoctor 기능을 사용하기 위해 :asciidoctor task에 asciidoctorExtensions 을 설정
tasks.named('asciidoctor') {
configurations "asciidoctorExtensions"
inputs.dir snippetsDir
dependsOn test
}
//:copyDocument task가 수행되면 index.html 파일이 src/main/resources/static/docs 에 copy 되며, copy된 index.html 파일은 API 문서를 파일 형태로 외부에 제공하기 위한 용도로 사용가능
task copyDocument(type: Copy) {
dependsOn asciidoctor//:asciidoctor task가 실행된 후에 task가 실행 되도록 의존성을 설정
from file("${asciidoctor.outputDir}") //"build/docs/asciidoc/" 경로에 생성되는 index.html을 copy
into file("src/main/resources/static/docs") //src/main/resources/static/docs" 경로로 index.html을 추가
}
//:build task가 실행되기 전에 :copyDocument task가 먼저 수행 되도록 합니다.
build {
dependsOn copyDocument
}
//애플리케이션 실행 파일이 생성하는 :bootJar task 설정
bootJar {
dependsOn copyDocument //:bootJar task 실행 전에 :copyDocument task가 실행 되도록 의존성을 설정
from ("${asciidoctor.outputDir}") { //Asciidoctor 실행으로 생성되는 index.html 파일을 jar 파일 안에 추가 -> 써 웹 브라우저에서 접속(http://localhost:8080/docs/index.html) 후, API 문서를 확인가능
into 'static/docs'
}
}
src/docs/asciidoc/
src/docs/asciidoc/
디렉토리 내에 비어있는 템플릿 문서(index.adoc
)를 생성해주면 된다.@WebMvcTest(MemberController.class) // (1)
@MockBean(JpaMetamodelMappingContext.class) // (2)
@AutoConfigureRestDocs // Spring Rest Docs에 대한 자동 구성
public class MemberControllerRestDocsTest {
@Autowired
private MockMvc mockMvc;
@MockBean
// (5) 테스트 대상 Controller 클래스가 의존하는 객체를 Mock Bean 객체로 주입 받기
@Test
public void postMemberTest() throws Exception {
// given
// (6) 테스트 데이터
// (7) Mock 객체를 이용한 Stubbing
// when
ResultActions actions =
mockMvc.perform(
// (8) request 전송
);
// then
actions
.andExpect(// (9) response에 대한 기대 값 검증)
.andDo(document( // (10) API 문서 스펙 정보 추가
"post-member", // (9-1) API 문서 스니핏의 식별자 역할
//문서 스니핏은 post-member 디렉토리 하위에 생성
getRequestPreProcessor(), // (9-2) 문서 영역을 전처리
getResponsePreProcessor(), // (9-3) 문서 영역을 전처리
pathParameters( // URL의 path variable의 정보
parameterWithName("member-id").description("회원 식별자")
),
requestFields( // (9-4) 문서로 표현될 request body를 의미
//파라미터로 전달되는 List<FieldDescriptor> 의 원소인 FieldDescriptor 객체가 request body에 포함된 데이터를 표현
List.of(
//request body를 JSON 포맷으로 표현 했을 때, 하나의 프로퍼티를 의미하는 FieldDescriptor
fieldWithPath("memberId").type(JsonFieldType.NUMBER).description("회원 식별자").
ignored(), // request body에 매핑되지 않는 정보로 ignored()를 추가해서 API 스펙 정보에서 제외
fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), // (9-5)
fieldWithPath("name").type(JsonFieldType.STRING).description("이름").optional(),
//optional()을 추가해서 API 스펙 정보에서 필수가 아닌 선택 정보로 설정
fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호")
)
),
responseFields( // (9-6) 문서로 표현될 response body를 의미
List.of(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("결과 데이터"),
fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"), // (9-7)
fieldWithPath("data.email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("data.name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("data.phone").type(JsonFieldType.STRING).description("휴대폰 번호"),
fieldWithPath("data.memberStatus").type(JsonFieldType.STRING).description("회원 상태"),
fieldWithPath("data.stamp").type(JsonFieldType.NUMBER).description("스탬프 갯수")
));
}
}
@WebMvcTest
@SpringBootTest
는 데이터베이스까지 요청 프로세스가 이어지는 통합 테스트에 주로 사용되고, @WebMvcTest
는 Controller를 위한 슬라이스 테스트에 주로 사용@MockBean
@EnableJpaAuditing
// 전처리작업에 사용
// preprocessRequest(prettyPrint()) 는 문서에 표시되는 JSON 포맷의 request body를 예쁘게 표현
public interface ApiDocumentUtils {
static OperationRequestPreprocessor getRequestPreProcessor() {
return preprocessRequest(prettyPrint());
}
static OperationResponsePreprocessor getResponsePreProcessor() {
return preprocessResponse(prettyPrint());
}
}
= 커피 주문 애플리케이션 // (1) API 문서의 제목
// API 문서의 목차와 관련된 내용
:sectnums:
:toc: left
:toclevels: 4
:toc-title: Table of Contents
:source-highlighter: prettify
Hwang Jung Sik <jungsik.hwang@codestates.com> // (2) API 문서를 생성한 이의 정보
v1.0.0, 2022.04.08 // (3) API 문서의 생성 날짜
// (4) 우리가 테스트 케이스 실행을 통해 생성한 API 문서 스니핏을 사용하는 부분
***
== MemberController
=== 회원 등록
.curl-request
include::{snippets}/post-member/curl-request.adoc[]
// include::{snippets}/스니핏 문서가 위치한 디렉토리/스니핏 문서파일명.adoc[]
.http-request
include::{snippets}/post-member/http-request.adoc[]
.request-fields
include::{snippets}/post-member/request-fields.adoc[]
.http-response
include::{snippets}/post-member/http-response.adoc[]
.response-fields
include::{snippets}/post-member/response-fields.adoc[]
=== 회원 정보 수정
.curl-request
include::{snippets}/patch-member/curl-request.adoc[]
.http-request
include::{snippets}/patch-member/http-request.adoc[]
.request-fields
include::{snippets}/patch-member/request-fields.adoc[]
.http-response
include::{snippets}/patch-member/http-response.adoc[]
.response-fields
include::{snippets}/patch-member/response-fields.adoc[]
:bootJar
또는 :build
task 명령을 더블 클릭src/main/resources/static/docs
’ 디렉토리에 index.adoc 파일을 이용해 변환된 index.html 파일이 생성된다.[http://localhost:8080/docs/index.html](http://localhost:8080/docs/index.html)
로 확인, =
: 문서의 제목을 작성. =
의 개수가 늘어날 수록 글자는 작아진다.
:sectnums:
: 목차에서 각 섹션에 넘버링
:toc:
는 목차를 문서의 어느 위치에 구성할 것인지를 설정.
:toclevels:
은 목차에 표시할 제목의 level을 지정합니다.
====
까지의 제목만 목차에 표시:toc-title:
은 목차의 제목을 지정
:source-highlighter:
문서에 표시되는 소스 코드 하일라이터를 지정. ex)prettify
***
는 단락을 구분 지을 수 있는 수평선을 추가
CAUTION:
을 사용해서 경고 문구를 추가(NOTE:
, TIP:
, IMPORTANT:
, WARNING:
등을 사용가능)
문단의 제목 다음에 한 라인을 띄우고 한 칸 들여쓰기의 문단을 작성하면 박스 문단을 사용가능
image::
를 사용해서 추가index.adoc
을 index.html
로 변환 후, 특정 디렉토리(src/main/resources/static/docs
)에 생성.curl-request // (1)
include::{snippets}/post-member/http-request.adoc[]
.
은 하나의 스니핏 섹션 제목을 표현하기 위해 사용
include
는 Asciidoctor에서 사용하는 매크로, 스니핏을 템플릿 문서에 포함할 때 사용
::
은 매크로를 사용하기 위한 표기법, {snippets}
는 해당 스니핏이 생성되는 디폴트 경로