안녕하세요 깐지와꾼지파파 입니다 !!! 그동안 글을 너~~~무 쓰지 않아.. 오랜만에 작성하네요..^^
오늘은 api 문서를 어떻게 아름답게 공유하면 좋을까 고민하다가 restdocs 를 발견하게 관련하여 글을 써 보려고 합니다.
작성에 앞서 자바 api 문서 자동화 라이브러리로는 주로 두가지를 많이 사용하는데요
Swagger, Spring Rest Docs 두 가지가 대표적입니다 !
1. 고민해야 할 이슈
1. RESTful 서비스에 대해 정확하고 읽기 쉬운 문서 생성 및 제공
2. 클라이언트 작업자와 서버 작업자 사이의 API 문서 버전 동기화
3. 테스트를 통하여 API 문서 자동 생성
2. Rest Docs 을 선택한 이유
1. 테스트를 성공해야만 문서가 만들어 진다.
2. 소스 자체에 영향이 없다
3. Spring REST Docs 의 장점
1. 별도 html, pdf 파일로 api 문서 자동생성 (호스팅 가능)
2. req/res 테스트를 통하여 api 문서가 생성되므로 신뢰성 증가
3. api 변경에 따른 api 문서 최신화 보장
4. Rest Docs 작성 버전
1. spring boot 2.5.1
2. gradle 7.1
3. java 11
4. jUnit5
5. MockMvc
6. asciidoctor 3.3.2
5. 예제 소스
build.gradle
plugins {
id 'org.springframework.boot' version '2.5.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "org.asciidoctor.jvm.convert" version "3.3.2" //1
}
group = 'site.heeseong'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
//2
ext {
snippetsDir = file('build/generated-snippets')
}
//3
asciidoctor {
dependsOn test
attributes 'snippets': snippetsDir
inputs.dir snippetsDir
}
//4
bootJar {
dependsOn asciidoctor
copy {
from "${asciidoctor.outputDir}"
into 'src/main/resources/static/docs'
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
//5
// https://mvnrepository.com/artifact/org.springframework.restdocs/spring-restdocs-mockmvc
testImplementation group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc', version: '2.0.5.RELEASE'
}
test {
useJUnitPlatform()
}
1 : AsciiDoc 파일을 변환해주고 build 폴더 위치에 복사하기 위한 플러그인, gradle 7.x 부터는 jvm 플러그인을 사용하지 않으면 build시 아래와 같은 오류가 나온다
- 'org.asciidoctor.convert' is deprecated. When you have time please switch over to 'org.asciidoctor.jvm.convert'.
2 : ext 블록 gradle의 모든 task 에서 사용할 수 있는 전역 변수 영역, 스니핏의 디렉토리 설정
3 : asciidoctor 설정, build 시 test > asciidoctor 순으로 실행
4 : build 시 asciidoctor > bootJar 순으로 실행
- jvm 플러그인 버전부터 asciidoctor의 스니핏 파일 생성 경로가 html -> build/docs/asciidoc 로 변경 되었다.
- 스니핏 파일들을 모아 /static/docs/ 폴더로 복사 하여 호스팅을 가능하게 만듬
5 : MockMvc 를 사용하여 Rest Docs 을 가능하게 해주는 라이브러리
test/**/ApiDocumentUtils
package site.heeseong.restdocs;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
public interface ApiDocumentUtils {
static OperationRequestPreprocessor getDocumentRequest() {
return preprocessRequest(
modifyUris() // 1
.scheme("https")
.host("docs.api.com")
.removePort(),
prettyPrint()); //2
}
// 3
static OperationResponsePreprocessor getDocumentResponse() {
return preprocessResponse(prettyPrint());
}
}
1: 문서상에 표기되는 기본값 변경
2: request 를 예쁘게 출력
3. reponse 를 예쁘게 출력
test/**/UserApiController
package site.heeseong.restdocs;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import site.heeseong.restdocs.service.UserApiService;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static site.heeseong.restdocs.ApiDocumentUtils.getDocumentRequest;
import static site.heeseong.restdocs.ApiDocumentUtils.getDocumentResponse;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs(uriScheme = "https", uriHost = "docs.api.com")
public class UserApiController {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserApiService userApiService;
@BeforeEach
public void setUpData(){
userApiService.saveTestUser();
}
@Test
public void selectUser() throws Exception{
ResultActions result = this.mockMvc.perform(
RestDocumentationRequestBuilders.get("/users/{idx}", 1L)
);
result.andExpect(status().isOk())
.andDo(
document("select-user"
, getDocumentRequest()
, getDocumentResponse()
, pathParameters(
parameterWithName("idx").description("고유 번호")
)
, responseFields(
fieldWithPath("idx").type(JsonFieldType.NUMBER).description("고유 번호")
, fieldWithPath("userId").type(JsonFieldType.STRING).description("아이디")
, fieldWithPath("email").type(JsonFieldType.STRING).description("이메일")
, fieldWithPath("phoneNumber").type(JsonFieldType.STRING).description("휴대폰 번호")
)
))
.andDo(print());
}
@Test
public void saveUser() throws Exception{
mockMvc.perform(
post("/users")
)
//.andDo(print())
.andExpect(status().isOk());
}
@Test
public void updateUser() throws Exception{
// //given
// User user = User.builder()
// .userId("테스트수정")
// .email("테스트 email 수정")
// .phoneNumber("010수정")
// .build();
//
//
// mockMvc.perform(
// put("/users", user)
// )
// .andExpect(status().isOk());
}
@Test
public void deleteUser() throws Exception{
// mockMvc.perform(
// delete("/users")
// )
// .andExpect(status().isOk());
}
}
테스트 수행시 build/generated-snippets/ 하위에 지정한 문자열의 폴더와 문서가 생성
위 생성된 파일들을 하나로 묶어 html 로 생성하기 위해서 asciidoc 셋팅을 해주어야하는데요
gradle 경우 src/docs/asciidoc 경로에 만들어 주어야 기능이 정상적으로 동작합니다.
https://github.com/hhsung0120/restdocs
필수 값 사용 여부는 다음 시간에 작성 하도록 하겠습니다 ~~~
참고 1 : https://asciidoctor.github.io/asciidoctor-gradle-plugin/master/user-guide/
참고 2 : https://techblog.woowahan.com/2597/
참고 3 : https://narusas.github.io/2018/03/21/Asciidoc-basic.html
'백앤드 이야기 > JAVA&Spring' 카테고리의 다른 글
[JAVA&기타] 네이밍 규칙 (0) | 2022.03.11 |
---|---|
[JAVA] Optional 기능 메모 (0) | 2021.07.13 |
[JPA] 메모 (0) | 2021.06.14 |
[JAVA] stream API 사용하기 (0) | 2021.06.09 |
[Gradle] gradle 명령어 (0) | 2021.04.21 |
댓글