
- Spring Boot 3.5.0 + Gradle 환경에서 바로 동작하도록 세팅해둔 리포지토리
https://github.com/gyehyun-bak/spring-rest-docs
- <빨랐죠> 리포지토리
https://github.com/gyehyun-bak/ppalatjyo
채팅으로 맞추는 퀴즈 웹 게임 <빨랐죠>를 개발을 시작하면서, 이전까지는 노션 등으로 수작업으로 관리하던 API 문서화를 이번 프로젝트에서는 Spring REST Docs를 활용하여 자동화해보려고 합니다.

이전 프로젝트를 진행하면서는 API 문서를 노션 템플릿을 만들어두고 매번 수작업으로 생성/최신화를 하였습니다. 하지만 변경이 많은 개발 특성상 아무리 신경쓰려 해도 놓치는게 생기게 되고, 그러면 자연스럽게 팀을 혼란스럽게 하여 더 많은 일을 만들게 되고, 비생산적인 일로 팀을 지치게 만든다는 것을 느꼈습니다.
그리고 귀찮습니다! 핵심 비즈니스 로직 개발도 바쁜데, 매번 문서 수정을 수작업으로 하고 있으려고 하면 여간 귀찮은게 아닙니다. 수작업으로 하다 보니 오타 등으로 발생하는 오류는 덤입니다(왜 현실에는 컴파일 에러가 없는가!).
그런데 이런 문제를 해결해줄 수 있는 것이 API 문서 자동화 툴입니다. 자동화 툴로 잘 알려진 것으로는 Swagger와 Spring REST Docs가 있습니다.
Swagger는 어노테이션을 기반으로 API 문서를 자동으로 생성해주며 간단한 테스트 환경을 제공합니다.
@RestController
@RequestMapping("/api/hello")
public class HelloController {
@GetMapping
@Operation(summary = "Hello 메시지 반환", description = "기본 Hello 메시지를 반환합니다.")
public String sayHello() {
return "Hello, Swagger!";
}
}

Spring Boot에서 사용시 서버의 swagger-ui 화면으로 접속하면 위와 같은 자동 생성된 문서를 볼 수 있습니다. (출처: https://petstore.swagger.io/#/store/getInventory)

이렇게 각 API의 상세 정보를 제공하고 해당 API로 요청을 날리고 반환값을 표시해주는 UI 환경을 제공합니다.
이런 API 테스트 환경(Try it out)이 상당히 매력적입니다.
Spring REST Docs는 어노테이션이 아닌 Spring MVC Test 또는 WebTestClient로 작성된 테스트를 기반으로 API 문서를 자동으로 생성해주는 툴입니다.
테스트에 사용된 메서드를 기반으로 각각 요청과 응답, 응답 바디 등등에 해당하는 Snippet(스니펫)을 자동 생성해줍니다. 또한 필요에 따라 사용자 정의 스니펫을 만들 수 있습니다.
// 실제 API가 포함된 컨트롤러
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello World";
}
}
// 컨트롤러에 대한 테스트
@AutoConfigureMockMvc
@AutoConfigureRestDocs(outputDir = "build/generated-snippets")
@SpringBootTest
class HelloControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andDo(print())
.andDo(document("hello"));
}
}
컨트롤러에 작성된 테스트에 따라서 각종 파라미터, 응답 데이터 등 자동으로 포함됩니다.

서로 장단점이 있지만 제가 Spring REST Docs를 선택한 이유는 다음과 같습니다.
원하는 결과를 위해서 여러 어노테이션을 적극 추가해야하고, 이렇게 되면 비즈니스(운영) 코드에 비즈니스와 관련 없는, 오직 문서화를 위한 어노테이션이 늘어나게 됩니다. Spring REST Docs는 테스트 코드를 기반으로 문서를 생성하고, 운영 코드에는 아무것도 추가할 필요가 없기 때문에 이러한 단점이 없습니다.
API 문서 생성을 위해 테스트를 필요로 하기 때문에 자연스럽게 테스트 작성이 강제됩니다. 따라서 자연스럽게 컨트롤러 단에 대한 테스트 작성이 강제됩니다. TDD를 적극 도입하려고 하는 입장에서 이런 강제 사항들은 아주 구미가 당겼습니다.
또한 코드로 증명 가능한(또한 자동화된) 테스트이기 때문에 생성된 API 문서 내 내용이 반드시 보장됩니다. 필드 이름, URL 작성 실수라던가 있을 수가 없습니다. 자연스럽게 프론트엔드와의 협업 신뢰도도 상승할 수 있습니다("제 쪽에서 한 번 확인해볼게요." 할 일이 줄어듦).
대신 클라이언트 측에서 API 테스트 편의성이 좋지 않다거나, 설정이 복잡하다거나, 설정이 참 뭣하게 어렵달라거나(공식 문서만 따라하면 동작을 안 합니다), UI가 밋밋하다는 단점(?)이 있습니다.
Spring REST Docs로 서버에서 API 문서를 제공하기 위해서는 아래와 같은 과정이 필요합니다.
build.gradle에 필요한 의존성을 추가하고 테스트 및 빌드시 자동으로 스니펫을 생성하고 최종적으로 index.html을 생성할 수 있도록 설정을 추가해줍니다.하나씩 순서대로 설명하며, 최종 결과물은 아래 링크에서 확인하실 수 있습니다.
build.gradle이 부분은 블로그 포스팅, 영상마다 좀 다릅니다. 저는 https://start.spring.io/ 에서 Spring REST Docs 의존성을 추가해서 자동으로 생성되는 프로젝트의 설정을 바탕으로 최소한의 설정만 추가하여 동작하도록 노력했습니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.0'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
ext {
set('snippetsDir', file("build/generated-snippets"))
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
tasks.named('asciidoctor') {
inputs.dir snippetsDir
attributes 'snippets': snippetsDir // index.adoc 내 {snippets} 위치 명시
dependsOn test
}
// Asciidoctor로 생성한 HTML 문서를 static/docs로 복사 (내장 톰캣에서 제공되게 함)
tasks.register('copyDocs', Copy) {
dependsOn asciidoctor
doFirst {
delete "src/main/resources/static/docs/index.html" // 기존 index.html 삭제
}
from asciidoctor.outputDir // asciidoctor 결과물 경로
into "src/main/resources/static/docs" // 정적 리소스로 이동시켜 웹 접근 가능하게 함
}
// build 시 자동으로 문서 생성 및 복사까지 수행되게 설정
tasks.named('build') {
dependsOn copyDocs
}
task를 통해 Gradle 빌드가 이루어지기 전
asciidoctor 플러그인을 통해 index.adoc을 기반으로 index.html을 생성하고, @RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello World";
}
}

단순 "Hello World!"를 반환하는 컨트롤러입니다.
@AutoConfigureMockMvc
@AutoConfigureRestDocs
@SpringBootTest
class HelloControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andDo(print())
.andDo(document("hello"));
}
}
MockMvc를 사용하였습니다. mockMve를 사용하면 이렇게 메서드 체이닝으로 해야할 동작을 정의할 수 있습니다. 단순히, "/hello"에 요청을 보내면 내용물로 "Hello World!"가 반환되어야 함을 의미합니다.
@AutoConfigureRestDocs(outputDir = "build/generated-snippets") 와 같은 방식으로 스니펫을 저장할 위치를 지정해줄 수도 있습니다. 기본은 /build/generated-snippets 이므로 생략 가능합니다.
andDo(document("hello")) 를 추가하면 "hello"라는 이름의 API Rest Docs 스니펫이 생성됩니다. 이 이름은 이후 index.adoc에서 사용됩니다.

설정에 문제가 없다면, 테스트를 실행하고 나면 위와 같이 build 폴더 안에 generated-snippets 폴더가 생성됩니다.
/src/docs/asciidoc/ 디렉토리를 생성 후 index.adoc 파일을 생성해줍니다.
= API 문서
:toc: left
:toclevels: 2
== Hello API
=== /hello
.request
include::{snippets}/hello/http-request.adoc[]
.response
include::{snippets}/hello/http-response.adoc[]
include::{snippets}/hello/response-body.adoc[]
문서 작성은 이제 입맛에 맞게 해주시면 됩니다. adoc 문법을 참고하세요.
=== /hello 처럼 API를 하나씩 추가해주고, 원하는 자리에 만들어둔 스니펫을 그대로 가져다 삽입할 수 있습니다.
include::{snippets}/hello/http-response.adoc[]에서 본래 {snippets} 자리에 자동으로 outputDir 경로가 들어가야 되는데 Gradle을 아직 잘 몰라서인지 빌드 과정에 outputDir가 주입이 안 되는 문제가 있었습니다. 따라서 앞선build.gradle에attributes 'snippets': snippetsDir을 추가해주었습니다.

/docs/index.html 로 접속하면 위와 같이 생성된 문서를 받아볼 수 있습니다.
include::{snippets}/hello/http-response.adoc[] 등으로 적어둔 자리에 자동 생성된 스니펫이 들어간 것을 확인할 수 있습니다.
항상 곁에 두고도 Gradle에 대해 많이 몰랐다는 것을 느꼈습니다.
이번 기회에 Gradle을 적극적으로 다뤄보며 빌드 과정도 단순한 일이 아니란 것을 알게 되었습니다.
아무튼 동작을 하니 컨트롤러 테스트 작성과 API 문서화에 힘써보고자 합니다.