API 문서화
REST API 요청 정보(요청 URL, request body, query param 등)의 문서화 하여
API 문서(스펙)를 만드는 과정
Swagger
@ApiOperation@ApiResponses@ApiResponse
@ApiModel@ApiModelProperty
등의 애너테이션을 일일히 적용하여 문서화
API 툴로서의 기능을 활용할 수 있는 장점이 있으나
가독성, 유지보수성이 떨어지며 API문서와 실제 코드간의 불일치가 발생 할 수 있다
테스트 코드 기반의 자동 문서화 Spring 프로젝트
검증 부분에 Spring Rest Docs의 API 문서화 코드를 추가하여 생성
컨트롤러에서 구현한 Request Body, Response Body, Query Param와 일치해야 passed
테스트 케이스가 passed 되면 bulid에 API문서가 생성된다
Spring Rest Docs 문서화 과정
슬라이스 테스트 코드 -> API스펙 코드 ->
Test(테스트 클래스를 실행하거나 빌드(test task)를 실행) -> passed
API문서 스니펫 생성(.adoc) -> API 문서 생성 -> HTML로 변환테스트 케이스 하나당 하나의 스니펫이 생성
생성된 API 문서 스니펫을 모아서 하나의 API 문서로 생성
** 프로그래밍에서 스니펫은 재사용되는 작은 코드 조각이나 메서드를 말한다
plugins {
id 'org.springframework.boot' version '2.7.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "org.asciidoctor.jvm.convert" version "3.3.2" // Asciidoctor를 사용하기 위한 플러그인
id 'java'
}
group = 'com.codestates'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
// ext 변수의 set() 메서드, 스니펫이 생성될 경로를 지정
ext {
set('snippetsDir', file("build/generated-snippets"))
}
// AsciiDoctor의 의존 그룹 지정
configurations {
asciidoctorExtensions
}
dependencies {
// spring-restdocs-core와 spring-restdocs-mockmvc 의존 라이브러리가 추가
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
// asciidoctor 의존 라이브러리를 추가(asciidoctorExtensions 그룹에 의존 라이브러리 포함)
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.mapstruct:mapstruct:1.5.1.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'com.google.code.gson:gson'
}
// 스니펫 디렉토리 경로를 설정
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
// :asciidoctor task에 asciidoctorExtensions을 설정(Asciidoctor 기능 사용을 위해)
tasks.named('asciidoctor') {
configurations "asciidoctorExtensions"
inputs.dir snippetsDir
dependsOn test
}
// build task 실행 전 asciidoctor 실행, index.html를 경로에 copy(외부에 제공용)
task copyDocument(type: Copy) {
dependsOn asciidoctor //task 실행 전 asciidoctor task 실행
from file("${asciidoctor.outputDir}") // index.html을 copy
into file("src/main/resources/static/docs") //경로로 index.html을 추가
}
build {
dependsOn copyDocument // copyDocument task -> build task -> asciidoctor task
}
// bootJar task 설정(애플리케이션 실행 파일이 생성)
bootJar {
dependsOn copyDocument // copyDocument task -> bootJar task
from ("${asciidoctor.outputDir}") {
into 'static/docs' // index.html 파일을 jar 파일 안에 추가, (웹에서 확인용)
// http://localhost:8080/docs/index.html 으로 접속하여 API 확인
}
}
위 설정 후 테스트케이스 작성, API 스펙정보 추가
document()메서드로 스니펫을 생성한다OperationRequestPreprocessor/OperationResponsePreprocessor requestFields() -> List<FieldDescriptor>responseFields() -> List<FieldDescriptor>@WebMvcTest(MemberController.class) //Controller 클래스를 지정
@MockBean(JpaMetamodelMappingContext.class) //최상위 Application 클래스 설정
//@EnableJpaAuditing을 Application에 설정할 것
@AutoConfigureRestDocs // Spring Rest Docs 자동 구성
public class MemberControllerRestDocsTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private Gson gson;
@MockBean
private MemberService memberService;
@Test
public void postMemberTest() throws Exception {
...
// then
actions
.andExpect(status().isCreated())
.andExpect(header().string("Location", is(startsWith("/v11/members/"))))
.andDo(document(//**API 스펙 정보를 전달받아 문서화하는 메서드**
"post-member", // API 문서 스니펫의 식별자, post-member 디렉토리 하위에 생성됨
getRequestPreProcessor(), getResponsePreProcessor(),// 스니펫 생성 전 문서영역을 전처리하는 메서드를 따로 설정
requestFields( //출력 될 request body
List.of(
fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호"))),
responseHeaders( // 출력 될 response header
headerWithName(HttpHeaders.LOCATION).description("Location header. 등록된 리소스의 URI"))
));
}
}
테스트 실행 후 빌드 된 파일 확인

제외된 파일 표기를 체크 해제한 줄도 모르고 빌드파일을 찾느라 해맸다는 슬픈 사연이 있다
.ignored()로 설정.optional()로 설정.andDo(document(
"patch-member",
getRequestPreProcessor(), getResponsePreProcessor(),
pathParameters(parameterWithName("member-id").description("회원 식별자")), //path variable 추가
requestFields(
List.of(
fieldWithPath("memberId").type(JsonFieldType.NUMBER).description("회원 식별자").ignored(), //request body에 매핑되지 않는 정보임으로 제외
fieldWithPath("name").type(JsonFieldType.STRING).description("이름").optional(), // 선택적으로 수정가능함
fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호").optional(),
fieldWithPath("memberStatus").type(JsonFieldType.STRING).description("회원 상태: MEMBER_ACTIVE / MEMBER_SLEEP / MEMBER_QUIT").optional())),
responseFields( // response body
List.of(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("결과 데이터"),
fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
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("스탬프 갯수")))
));

Asciidoc
텍스트 기반 문서 포맷
Spring Rest Docs를 통해 생성
기술 문서 작성을 위해 설계된 가벼운 마크업 언어
=~====font level
:sectnums:섹션 넘버링
:toc:목차 위치
:toclevels:목차에 표시할 제목의 level (4 라면 ==== 까지)
:toc-title:목차 제목
:source-highlighter:소스 코드 하일라이터
***수평선 추가
제목 다음 라인 들여쓰기시 박스문단 사용
CAUTION:NOTE:TIP:IMPORTANT:캡션사용
image::[URI]이미지 추가
스니펫 등록
.curl-request스니펫 섹션 제목
include스니펫을 등록::매크로 사용 표기법{snippets}스니펫 생성 기본경로
.request-fields
include::{snippets}/post-member/request-fields.adoc[]
build 하거나(build.gradle 설정 경로에 생성)
Asciidoctor 플러그인 기능에서 HTML로 변환(템플릿 문서가 있는 경로에 생성)

HTML 파일을 브라우저로 실행한 화면