티스토리 뷰
❓ Spring REST Docs?
: 테스트 코드를 통한 API 문서 자동화 도구
: 기본적으로 AsciiDoc을 사용하여 문서를 작성
❔ AsciiDoc
: 마크다운과 같은 경량 마크업 언어
장점
- 테스트를 통과해야 문서가 만들어진다 (신뢰도↑)
- 프로덕션 코드에 비침투적이다
단점
- 코드의 양이 많다
- 설정이 어렵다
간단한 예시들과 함께 사용 방법을 익혀보자!
설정
1️⃣ 플러그인 적용
: Asciidoc을 HTML 문서로 변환해주는 플러그인 적용
// build.gradle
plugins {
...
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
❕ 필수는 아니지만, AsciiDoc 플러그인을 설치하면 AsciiDoc 미리보기를 할 수 있다!
▪️ 설정 > Plugins > Marketplace > asciiDoc 플러그인 설치
2️⃣ configuration 설정 추가
configurations {
...
asciidoctorExt
}
3️⃣ 의존성 추가
dependencies {
...
// RestDocs
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
4️⃣ task 추가
ext { // 전역 변수
snippetsDir = file('build/generated-snippets') // 스니펫에 대한 경로 지정
}
test {
outputs.dir snippetsDir // 테스트 결과물을 스니펫 디렉토리로 지정
}
asciidoctor {
inputs.dir snippetsDir // 테스트 결과물을 가지고 수행
configurations 'asciidoctorExt' // 확장 플러그인 설정
sources { // 특정 파일만 html로 만든다.
include("**/index.adoc")
}
baseDirFollowsSourceFile() // 다른 adoc 파일을 include 할 때 경로를 baseDir로 맞춘다.
dependsOn test // test가 수행된 다음에 asciidoctor 수행
}
bootJar {
dependsOn asciidoctor // asciidoctor가 수행된 다음에 bootJar 수행
from("${asciidoctor.outputDir}") {
into 'static/docs' // 결과 문서를 정적 파일로 보기 위해 static/docs 하위에 복사
}
}
준비 완료.. 이제 진짜 API 문서를 만들어보자!
적용
1️⃣ test폴더 하위에 docs폴더 생성 후 REST Doc에 대한 설정 클래스(RestDocsSupport.class) 추가
: 통합 테스트에서 공통 환경을 뽑는 느낌과 유사!
@ExtendWith(RestDocumentationExtension.class) // RestDoc에 대한 확장 추가
public abstract class RestDocsSupport {
protected MockMvc mockMvc;
protected ObjectMapper objectMapper = new ObjectMapper();
@BeforeEach
void setUp(RestDocumentationContextProvider provider) {
this.mockMvc = MockMvcBuilders.standaloneSetup(initController())
.apply(documentationConfiguration(provider))
.build();
}
protected abstract Object initController(); // 하위 클래스에서 컨트롤러를 지정할 수 있도록
}
💁🏻♀️ Mockvc를 MockMvcBuilders.standaloneSetup()과 MockMvcBuilders.webAppContextSetup()으로 생성이 가능! but webAppContext의 경우 @SpringBootTest로 문서를 작성할때도 SpringBoot를 띄워야 함! 그러므로 standalone 방식으로 간단하게 MockMvc를 구성했다!
2️⃣ Docs를 위한 Test 코드 작성
public class ProductControllerDocsTest extends RestDocsSupport { // 아까 만든 설정 클래스를 상속하도록
private final ProductService productService = mock(ProductService.class); // 서비스 Mocking
@Override
protected Object initController() {
return new ProductController(productService); // REST Docs 테스트는 Spring 의존성이 없음으로 직접 생성
}
@DisplayName("신규 상품을 등록하는 API")
@Test
void createProduct() throws Exception {
ProductCreateRequest request = ProductCreateRequest.builder()
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.SELLING)
.name("아메리카노")
.price(4000)
.build();
given(productService.createProduct(any(ProductCreateServiceRequest.class)))
.willReturn(ProductResponse.builder()
.id(1L)
.productNumber("001")
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.SELLING)
.name("아메리카노")
.price(4000)
.build()
); // 서비스 Stubbing
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isOk()) // 이 아래부터 REST Docs
.andDo(document("product-create", // 테스트에 대한 일종의 id 값 (마음대로 정하기)
preprocessRequest(prettyPrint()), // 코드를 예쁘게 보여주게 설정
preprocessResponse(prettyPrint()),
requestFields( // 요청 바디 스니펫
fieldWithPath("type").type(JsonFieldType.STRING) // Enum은 STRING
.description("상품 타입"),
fieldWithPath("sellingStatus").type(JsonFieldType.STRING)
.optional() // optiona 하다고 명시
.description("상품 판매상태"),
fieldWithPath("name").type(JsonFieldType.STRING)
.description("상품 이름"),
fieldWithPath("price").type(JsonFieldType.NUMBER)
.description("상품 가격")
),
responseFields( // 응답 바디 스니펫
fieldWithPath("code").type(JsonFieldType.NUMBER)
.description("코드"),
fieldWithPath("status").type(JsonFieldType.STRING)
.description("상태"),
fieldWithPath("message").type(JsonFieldType.STRING)
.description("메시지"),
fieldWithPath("data").type(JsonFieldType.OBJECT) // 객체는 OBJECT
.description("응답 데이터"),
fieldWithPath("data.id").type(JsonFieldType.NUMBER)
.description("상품 ID"),
fieldWithPath("data.productNumber").type(JsonFieldType.STRING)
.description("상품 번호"),
fieldWithPath("data.type").type(JsonFieldType.STRING)
.description("상품 타입"),
fieldWithPath("data.sellingStatus").type(JsonFieldType.STRING)
.description("상품 판매상태"),
fieldWithPath("data.name").type(JsonFieldType.STRING)
.description("상품 이름"),
fieldWithPath("data.price").type(JsonFieldType.NUMBER)
.description("상품 가격")
)
));
}
}
3️⃣ 테스트를 수행하고, asciidoctor 테스크 수행
✔️ gradle > documentation > asciidoctor
✔️ build > generated-snippets에 adoc 파일들이 생성된 것을 확인
4️⃣ adoc 파일들을 하나의 문서로 만들기
✔️ src > docs > asciidoc > index.adoc 파일 생성 (docs, asciidoc폴더들도 만들어 주기)
ifndef::snippets[]
:snippets: ../../build/generated-snippets
endif::[]
= REST API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left // 목차
:toclevels: 2 // 목차 레벨
:sectlinks:
[[Product-API]]
== Product API
include::api/product/product.adoc[]
❕ build.gradle의 asciidoctor 테스크에 baseDirFollowsSourceFile()를 지정해 주었기 때문에 include 경로는 src > docs > asciidoc 폴더 하위에서부터 지정 (여기서는 product.adoc[]의 위치가 src > docs > asciidoc > api > product > product.adoc[])
Asciidoc 기본 사용법
Asciidoc의 기본 문법을 설명한다
narusas.github.io
❕ index.adoc를 수정했다면 adoc문서를 html로 바꿔주기 위해 build 테스크 다시 수행
마지막으로 adoc을 커스텀하기 위해 템플릿을 만들어 보자
1️⃣ test > resources > org > springframework > restdocs > templates 폴더 생성
2️⃣ request-fields.snippet으로 request에 대한 템플릿 만들기
❕ 이렇게 하면 자동으로 템플릿으로 인지
==== Request Fields
|===
|Path|Type|Optional|Description // Optional 필드 추가
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} // Optional 이면 O를 출력
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===
3️⃣ response-fields.snippet으로 response에 대한 템플릿 만들기
==== Response Fields
|===
|Path|Type|Optional|Description
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===
4️⃣ build 테스크를 실행한 뒤 생성된 jar 파일을 실행 후 {ip}:8080/docs/index.html에 요청하면 API를 확인할 수 있음
❕ build > libs 폴더 아래에 jar 파일이 생성 된다.
참고링크
📎 스프링 공식 문서 - Spring REST Docs
Spring REST Docs
Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test or WebTestClient.
docs.spring.io
📎 인프런 - Practical Testing: 실용적인 테스트 가이드
학습 페이지
www.inflearn.com
Asciidoctor | A fast, open source text processor and publishing toolchain for converting AsciiDoc content to HTML5, DocBook, PDF
This project adheres to semantic versioning (major.minor.patch). Typically, patch releases are only made for the current minor release. However, exceptions are made on a case-by-case basis to address security vulnerabilities and other high-priority issues.
asciidoctor.org
'백엔드 > Spring' 카테고리의 다른 글
테스트 코드에 parameterized tests를 적용해보자! (1) | 2023.11.20 |
---|---|
스프링에서 로깅을 해보자📝 (0) | 2023.10.17 |
스프링 프레임워크의 핵심 개념들 (2) | 2023.10.10 |
[오류] JPAQueryFactory could not be found (0) | 2023.07.25 |
[스프링] 단축키 모음 (0) | 2023.07.07 |