문서를 제작할 때 유의점

위의 이미지를 보면 부족한 정보가 있다
- 호스트 주소을 알려줬는가 origin 주소
- http://naver.com
- insert 할 때 필드 값이 어떤 것이 있는지
- name, age, color 등
- 어떤 형식으로 데이터를 보내야 하는지
- Content-Type → json, form, text 등
각 방식의 장단점
Swagger API
스웨거는 개발자가 REST 웹 서비스를 설계, 빌드, 문서화, 소비하는 일을 도와주는 대형 도구 생태계의 지원을 받는 오픈 소스 소프트웨어 프레임워크이다.
swagger 설정
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'shop.mtcoding'
version = '1.0'
java {
sourceCompatibility = '21'
}
- 현재 프로젝트 버전
dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
}
- 최신 버전 swagger 설치
- 최신 버전은 상세 설정이 필요없다
- commons-lang3 버전 3.5 이상
- commons-lang3 버전 낮아서 Swagger 내부 메서드 실패할 수 있다
swagger 실행 확인
http://localhost:8080/swagger-ui/index.html
- 프로젝트 실행 후 해당 주소로 접근

swagger 어노테이션 추가
☕UserController
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.util.Resp;
import java.util.Map;
@Slf4j
@Tag(name = "User API", description = "회원가입, 로그인, 회원정보 수정 등 사용자 관련 API")
@RequiredArgsConstructor
@RestController
public class UserController {
private final UserService userService;
private final HttpSession session;
@Operation(summary = "회원정보 수정", description = "로그인한 사용자의 비밀번호와 이메일을 수정합니다.")
@ApiResponse(responseCode = "200", description = "회원정보 수정 성공",
content = @Content(schema = @Schema(implementation = UserResponse.DTO.class)))
@PutMapping("/s/api/user")
public ResponseEntity<?> update(@Valid @RequestBody UserRequest.UpdateDTO reqDTO, Errors errors) {
User sessionUser = (User) session.getAttribute("sessionUser");
UserResponse.DTO respDTO = userService.회원정보수정(reqDTO, sessionUser.getId());
return Resp.ok(respDTO);
}
@Operation(summary = "유저네임 중복체크", description = "해당 유저네임이 이미 사용 중인지 확인합니다.")
@ApiResponse(responseCode = "200", description = "중복 여부 반환",
content = @Content(schema = @Schema(implementation = Map.class)))
@GetMapping("/api/check-username-available/{username}")
public ResponseEntity<?> checkUsernameAvailable(
@Parameter(description = "확인할 유저네임", example = "metacoding") @PathVariable("username") String username) {
Map<String, Object> respDTO = userService.유저네임중복체크(username);
return Resp.ok(respDTO);
}
@Operation(summary = "회원가입", description = "유저네임, 비밀번호, 이메일을 받아 회원가입을 진행합니다.")
@ApiResponse(responseCode = "200", description = "회원가입 성공",
content = @Content(schema = @Schema(implementation = UserResponse.DTO.class)))
@PostMapping("/join")
public ResponseEntity<?> join(
@Valid @RequestBody UserRequest.JoinDTO reqDTO,
Errors errors,
HttpServletResponse response,
HttpServletRequest request) {
log.debug(reqDTO.toString());
log.trace("트레이스ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.debug("디버그---------");
log.info("인포ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.warn("워닝ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.error("에러ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
String hello = request.getHeader("X-Key");
System.out.println("X-good : " + hello);
response.setHeader("Authorization", "jooho");
UserResponse.DTO respDTO = userService.회원가입(reqDTO);
return Resp.ok(respDTO);
}
@Operation(summary = "로그인", description = "유저네임과 비밀번호를 이용하여 로그인합니다.")
@ApiResponse(responseCode = "200", description = "로그인 성공",
content = @Content(schema = @Schema(implementation = UserResponse.TokenDTO.class)))
@PostMapping("/login")
public ResponseEntity<?> login(
@Valid @RequestBody UserRequest.LoginDTO loginDTO,
Errors errors,
HttpServletResponse response) {
UserResponse.TokenDTO respDTO = userService.로그인(loginDTO);
return Resp.ok(respDTO);
}
}
☕UserRequest
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
public class UserRequest {
@Data
public static class UpdateDTO {
@Schema(description = "비밀번호 (4~20자)", example = "1234")
@Size(min = 4, max = 20)
private String password;
@Schema(description = "이메일 주소", example = "user@example.com")
@Pattern(regexp = "^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,3}$", message = "이메일 형식으로 적어주세요")
private String email;
}
@Data
public static class JoinDTO {
@Schema(description = "유저네임 (2~20자, 특수문자/한글 불가)", example = "metacoding")
@Pattern(regexp = "^[a-zA-Z0-9]{2,20}$", message = "유저네임은 2-20자이며, 특수문자,한글이 포함될 수 없습니다")
private String username;
@Schema(description = "비밀번호 (4~20자)", example = "1234")
@Size(min = 4, max = 20)
private String password;
@Schema(description = "이메일 주소", example = "user@example.com")
@Pattern(regexp = "^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,3}$", message = "이메일 형식으로 적어주세요")
private String email;
public User toEntity() {
return User.builder()
.username(username)
.password(password)
.email(email)
.build();
}
}
@Data
public static class LoginDTO {
@Schema(description = "유저네임 (2~20자)", example = "metacoding")
@Pattern(regexp = "^[a-zA-Z0-9]{2,20}$", message = "유저네임은 2-20자이며, 특수문자,한글이 포함될 수 없습니다")
private String username;
@Schema(description = "비밀번호 (4~20자)", example = "1234")
@Size(min = 4, max = 20)
private String password;
@Schema(description = "자동 로그인 여부 (체크시 'on')", example = "on", nullable = true)
private String rememberMe; // check되면 on, 안되면 null
}
}
☕UserResponse
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
public class UserResponse {
@Data
public static class TokenDTO {
@Schema(description = "엑세스 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI...")
private String accessToken;
@Schema(description = "리프레시 토큰", example = "dGhpc0lzUmVmcmVzaFRva2Vu")
private String refreshToken;
@Builder
public TokenDTO(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
@Data
public static class DTO {
@Schema(description = "유저 ID", example = "1")
private Integer id;
@Schema(description = "유저 이름", example = "cos")
private String username;
@Schema(description = "이메일 주소", example = "cos@nate.com")
private String email;
@Schema(description = "생성일시", example = "2024-05-16T10:00:00")
private String createdAt;
public DTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.email = user.getEmail();
this.createdAt = user.getCreatedAt().toString();
}
}
}
swagger 실행 확인
User

로그인

회원가입

회원정보수정

유저네임중복체크

swagger의 단점
- 코드의 가독성을 해친다
- 많은 양의 어노테이션이 추가된다


RestDoc 생성
- build.gradle 세팅
- 플러그인에 추가
- 라이브러리 추가
- 설정 추가
- 테스트 폴더에 MyRestDoc 클래스 생성
- 통합 테스트 코드 MyRestDoc 상속 받고
actions.andDo
추가
- ./gradlew clean build 실행
1. 설정 파일 변경
build.gradle
의존성
dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
- 추가
- 버전 숫자가 없으면 무조건 최신 버전
플러그인
plugins {
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
- 추가
기타 설정
ext {
set('snippetsDir', file("build/generated-snippets"))
}
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
}
tasks.register('copyRestDocs', Copy) {
dependsOn tasks.named('asciidoctor')
from "${asciidoctor.outputDir}"
into "src/main/resources/static/docs"
}
tasks.named('bootJar') {
dependsOn tasks.named('copyRestDocs')
}
- 나머지 추가
2. 추상 클래스 만들기

MyRestDoc
package shop.mtcoding.blog;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
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.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
// 4. 문서 만들기
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080) // build 폴더에 adoc 파일 생성
@AutoConfigureMockMvc // MockMvc 클래스가 IoC에 로드 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능
@ExtendWith({SpringExtension.class, RestDocumentationExtension.class})
public abstract class MyRestDoc {
@Autowired
protected MockMvc mvc;
protected RestDocumentationResultHandler document;
@BeforeEach
public void documentSetUp() {
this.document = MockMvcRestDocumentation.document("{class-name}/{method-name}",
Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
Preprocessors.preprocessResponse(Preprocessors.prettyPrint()));
}
}
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080)
→ build 폴더에 adoc 파일 생성

@AutoConfigureMockMvc
→ MockMvc 클래스가 IoC에 로드 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능
UserControllerTest
package shop.mtcoding.blog.integre;
@Transactional
// 컨트롤러 통합 테스트
@AutoConfigureMockMvc // MockMvc 클래스가 IoC에 로드 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) // MOCK -> 가짜 환경을 만들어 필요한 의존관계를 다 메모리에 올려서 테스트
public class UserControllerTest {
@Autowired
private ObjectMapper om; // json <-> java Object 변환 해주는 객체. IoC에 objectMapper가 이미 떠있음
@Autowired
private MockMvc mvc; // 가짜 환경에서 fetch 요청하는 클래스 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능
@Test
public void join_test() throws Exception { // 이 메서드를 호출한 주체에게 예외 위임 -> 지금은 jvm 이다
// given -> 가짜 데이터
UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO();
reqDTO.setEmail("haha@nate.com");
reqDTO.setPassword("1234");
reqDTO.setUsername("haha");
String requestBody = om.writeValueAsString(reqDTO);
System.out.println(requestBody); // {"username":"haha","password":"1234","email":"haha@nate.com"}
// when -> 테스트 실행
ResultActions actions = mvc.perform( // 주소가 틀리면 터지고, json 아닌거 넣으면 터지고, 타입이 달라도 터지고. 따라서 미리 터진다고 알려줌
MockMvcRequestBuilders
.post("/join")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON)
);
// eye -> 결과 눈으로 검증
String responseBody = actions.andReturn().getResponse().getContentAsString();
System.out.println(responseBody); // {"status":200,"msg":"성공","body":{"id":4,"username":"haha","email":"haha@nate.com","createdAt":"2025-05-13 11:45:23.604577"}}
// then -> 결과를 코드로 검증 // json의 최상위 객체를 $ 표기한다
actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(200));
actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("성공"));
actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.id").value(4));
actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.username").value("haha"));
actions.andExpect(MockMvcResultMatchers.jsonPath("$.body.email").value("haha@nate.com"));
}
}
⬇ 다음과 같이 변경한다
package shop.mtcoding.blog.integre;
// 컨트롤러 통합 테스트
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080)
@Transactional
@AutoConfigureMockMvc // MockMvc 클래스가 IoC에 로드 | RestTemplate -> 자바스크립트의 fetch와 동일, 진짜 환경에 요청 가능
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) // MOCK -> 가짜 환경을 만들어 필요한 의존관계를 다 메모리에 올려서 테스트
public class UserControllerTest extends MyRestDoc {
@Autowired
private ObjectMapper om; // json <-> java Object 변환 해주는 객체. IoC에 objectMapper가 이미 떠있음
private String accessToken;
@Test
public void join_username_uk_fail_test() throws Exception { // 이 메서드를 호출한 주체에게 예외 위임 -> 지금은 jvm 이다
// given -> 가짜 데이터
UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO();
reqDTO.setEmail("ssar@nate.com");
reqDTO.setPassword("1234");
reqDTO.setUsername("ssar");
String requestBody = om.writeValueAsString(reqDTO);
// System.out.println(requestBody); // {"username":"haha","password":"1234","email":"haha@nate.com"}
// when -> 테스트 실행
ResultActions actions = mvc.perform( // 주소가 틀리면 터지고, json 아닌거 넣으면 터지고, 타입이 달라도 터지고. 따라서 미리 터진다고 알려줌
MockMvcRequestBuilders
.post("/join")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON)
);
// eye -> 결과 눈으로 검증
String responseBody = actions.andReturn().getResponse().getContentAsString();
//System.out.println(responseBody); // {"status":200,"msg":"성공","body":{"id":4,"username":"haha","email":"haha@nate.com","createdAt":"2025-05-13 11:45:23.604577"}}
// then -> 결과를 코드로 검증 // json의 최상위 객체를 $ 표기한다
actions.andExpect(MockMvcResultMatchers.jsonPath("$.status").value(400));
actions.andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("중복된 유저네임이 존재합니다"));
actions.andExpect(MockMvcResultMatchers.jsonPath("$.body").value(Matchers.nullValue()));
actions.andDo(MockMvcResultHandlers.print()).andDo(document);
}
}
public class UserControllerTest
extends MyRestDoc
→ 커스텀한 MyRestDoc을 상속 받는다
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080)
→ 이 어노테이션을 테스트 클래스 위에 추가한다
@Autowired private MockMvc mvc;
→ 삭제한다. 상속 받은 클래스에 있는 mvc를 사용할 것이다
actions.andDo(MockMvcResultHandlers.print()).andDo(
document
);
→ 테스트 메서드 맨 마지막 줄에 이 코드를 추가한다- 테스트의 결과를 출력하는데 출력물을 문서에 작성하라는 코드
- 테스트 결과를 문서로 작성하기 위한 코드
3. 문서 작성을 위한 설정
build.gradle
// 3. 문서 만들기
// 1. gradlew build or 서버실행(x -> 테스트 코드가 실행이 안됨)
// gradlew build (test코드 실행 -> 결과 build/generated-snippets 에 copy 한다. *.adoc 으로 만들어준다)
// set -> 변수 설정 set 은 file 주소다
ext {
set('snippetsDir', file("build/generated-snippets"))
}
// 2. gradlew build (test코드 실행 -> 진짜로 1번의 결과가 실행됨)
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
// 3. 테스트가 종료되면 asciidoctor 실행 - *.adoc 파일들을 가지고 하나의 .adoc 파일로 변경(미리 파일 만들어 놔야함 -> src/docs/asciidoc) 이곳에 만들어짐
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test // test 를 실행 후에 실행(의존한다)
}
// 4. HTML 파일을 서버에서 열어볼 수 있게 Copy
tasks.register('copyRestDocs', Copy) {
dependsOn tasks.named('asciidoctor')
from "${asciidoctor.outputDir}" // asciidoctor 결과 위치
into "src/main/resources/static/docs"
}
tasks.named('bootJar') {
dependsOn tasks.named('copyRestDocs')
}
build.gradle 설정 설명
ext {
set('snippetsDir', file("build/generated-snippets"))
}
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
}
bootJar {
dependsOn asciidoctor
copy {
// src/docs/asciidoc == from 경로
from "${asciidoctor.outputDir}"
into 'src/main/resources/static/docs' // /static/docs로 복사!
}
}
야 결국 이거자나
build를 하면 test코드가 실행되는데
test코드가 실행된 결과를 build/generated-snippets 폴더에
파일로 저장하고
그 결과들을 static/docs 폴더에 html로 만들어줘
이 설정하는 거자나?
근데 왤케 복잡하게 되있어!! 그지같네 그치?
ChatGPT의 말:
응 정확히 이해했어! 너가 요약한 게 딱 맞아:
test를 실행하면 build/generated-snippets 폴더에 REST Docs 스니펫(.adoc 조각들)이 생기고,
asciidoctor가 이걸 기반으로 HTML 문서를 생성하고,
bootJar 시점에 이 HTML 결과물을 src/main/resources/static/docs로 복사해서
최종적으로
같은 경로에서 접근 가능하게 만드는 흐름이야.
근데 너 말대로 복잡해 보여도 이유는 있어. 요건 "빌드 프로세스 자동화"를 위해 필요한 의식 같은 거야 😅
요소 역할
snippetsDir 테스트에서 생성될 스니펫의 경로 정의
test.outputs.dir test task가 이 디렉토리를 출력물로 생성함을 명시
asciidoctor.inputs.dir AsciiDoctor가 어떤 파일을 기반으로 HTML 만들지를 정의
bootJar.copy HTML 결과물을 JAR에 포함시키기 위한 복사 작업
그럼 이걸 더 단순하게 만들 수 없냐?
가능은 해. 예를 들면, 아래처럼 Gradle Kotlin DSL로 쓰면 좀 더 명시적이면서 깔끔해:
kotlin
복사
편집
val snippetsDir by extra { file("build/generated-snippets") }
tasks.test {
outputs.dir(snippetsDir)
useJUnitPlatform()
}
tasks.asciidoctor {
inputs.dir(snippetsDir)
dependsOn(tasks.test)
}
tasks.bootJar {
dependsOn(tasks.asciidoctor)
doLast {
copy {
from("build/docs/asciidoc")
into("src/main/resources/static/docs")
}
}
}
결론
✅ 네 말이 맞아. 결국 "test → snippets → asciidoctor → 복사" 이게 핵심인데,
DSL 구조랑 자동화를 위한 규칙들 때문에 좀 장황하게 보이는 거야.
하지만 한 번 설정해두면, 문서 자동생성은 진짜 강력한 무기지.
필요하면 내가 이거 zip으로 자동 설정되게 구성해서 보내줄 수도 있어.
말만 해!
나의 말:
그리고 bootJar 시점도 별로야!! 그냥 서버실행하면 이자나
ChatGPT의 말:
맞아 ㅋㅋ 너 말 진짜 핵심 찔렀다.
bootJar 시점에 복사하는 건 "배포용 jar 만들 때 문서 포함시키려고" 그런 건데,
우린 그냥 서버 실행(bootRun)만 해도 문서 나오면 되잖아?
서버 실행할 때 docs/index.html 바로 확인 가능한 게 더 현실적이고 빠르지.
굳이 jar 만들 때까지 기다릴 이유가 없어.
✅ 그러면 실시간 미리보기 + 서버실행에 맞는 구조는?
1. 테스트 → snippets 생성
2. asciidoctor → HTML 생성
3. HTML을 static/docs로 바로 복사 (서버 실행하면 바로 열람 가능)
✨ 이렇게 바꿔 쓰면 돼 (Gradle 기준)
groovy
복사
편집
ext {
snippetsDir = file('build/generated-snippets')
docsOutputDir = file('src/main/resources/static/docs') // 여기에 바로 복사
}
// 테스트하면 스니펫 생성
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
// 문서 빌드 시 스니펫 의존
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
}
// 문서 결과물을 바로 static/docs로 복사
tasks.register('copyDocs', Copy) {
dependsOn tasks.named('asciidoctor')
from file("build/docs/asciidoc")
into docsOutputDir
}
// bootRun 전에 문서 복사
tasks.named('bootRun') {
dependsOn tasks.named('copyDocs')
}
📌 효과
./gradlew bootRun만 하면:
테스트 돌고,
문서 뽑고,
static/docs로 복사되고,
서버 켜지면 /docs/index.html 바로 열람 가능
각각의 설정 설명
// 1. gradlew build or 서버실행(x -> 테스트 코드가 실행이 안됨)
// gradlew build (test코드 실행 -> 결과 build/generated-snippets 에 copy 한다. *.adoc 으로 만들어준다)
// set -> 변수 설정 set 은 file 주소다
ext {
set('snippetsDir', file("build/generated-snippets"))
}
- gradlew build or 서버 실행(x -> 테스트 코드가 실행이 안됨)
- gradlew build (test코드 실행 -> 결과 build/generated-snippets 에 copy 한다. *.adoc 으로 만들어준다)
- set -> 변수 설정 set 은 file 주소다
- 1번에선 주소를 변수 설정하는 것
// 2. gradlew build (test코드 실행 -> 진짜로 1번의 결과가 실행됨)
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
- gradlew build (test코드 실행 -> 진짜로 1번의 결과가 실행됨)
- 여기서 테스트 코드 실행
// 3. 테스트가 종료되면 asciidoctor 실행 - *.adoc 파일들을 가지고 하나의 .adoc 파일로 변경(미리 파일 만들어 놔야함 -> src/docs/asciidoc) 이곳에 만들어짐
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test // test 를 실행 후에 실행(의존한다)
}
- 테스트가 종료되면 asciidoctor 실행 - *.adoc 파일들을 가지고 하나의 .adoc 파일로 변경(미리 파일 만들어 놔야 함 -> src/docs/asciidoc) 이 문서에 합쳐진다
- api.adoc 이라는 파일을 만들어야 함. restdoc 문법임
- 위 문서의 문법에 따라 만들어진 *.adoc 파일들을 가져와서 합쳐줌
- 빌드 시 만들어지는 각각의 .adoc파일들
- 작성한 .adoc 파일에 include 코드에 각 .adoc파일의 내용이 포함됨

api.adoc
ifndef::snippets[]
:snippets: ./build/generated-snippets
endif::[]
:user: user-controller-test
:board: board-controller-test
:reply: reply-controller-test
:love: love-controller-test
= REST API
:toc: left
:toclevels: 2
:source-highlighter: highlightjs
== 회원
=== 1. 회원가입 유저네임 중복 실패
===== Request Example
include::{snippets}/{user}/join_username_uk_fail_test/http-request.adoc[]
===== Response Example
include::{snippets}/{user}/join_username_uk_fail_test/http-response.adoc[]
=== 2. 회원가입
===== Request Example
include::{snippets}/{user}/join_test/http-request.adoc[]
===== Response Example
include::{snippets}/{user}/join_test/http-response.adoc[]
=== 3. 로그인
===== Request Example
include::{snippets}/{user}/login_test/http-request.adoc[]
===== Response Example
include::{snippets}/{user}/login_test/http-response.adoc[]
=== 4. 회원정보수정
===== Request Example
include::{snippets}/{user}/update_test/http-request.adoc[]
===== Response Example
include::{snippets}/{user}/update_test/http-response.adoc[]
=== 4. 회원정보수정
===== Request Example
include::{snippets}/{user}/update_test/http-request.adoc[]
===== Response Example
include::{snippets}/{user}/update_test/http-response.adoc[]
=== 5. 유저네임중복확인
===== Request Example
include::{snippets}/{user}/check_username_available_test/http-request.adoc[]
===== Response Example
include::{snippets}/{user}/check_username_available_test/http-response.adoc[]
== 게시글
=== 1. 게시글등록
===== Request Example
include::{snippets}/{board}/list_test/http-request.adoc[]
===== Response Example
include::{snippets}/{board}/list_test/http-response.adoc[]

// 4. HTML 파일을 서버에서 열어볼 수 있게 Copy
tasks.register('copyRestDocs', Copy) {
dependsOn tasks.named('asciidoctor')
from "${asciidoctor.outputDir}" // asciidoctor 결과 위치
into "src/main/resources/static/docs"
}
tasks.named('bootJar') {
dependsOn tasks.named('copyRestDocs')
}
- 빌드시 만들어지는 docs/asciidoc/api.html 파일을 → static.docs/api.html 로 복사



4. gradlew clean build 실행

- build/generated-snippets 폴더 생성
- 폴더 내부에 각각의 통합 테스트 클래스 이름의 폴더 생성
- 테스트 이후에 만들어지는 폴더 이름은 기본적으로 _ 방식으로 만들어진다
- 테스트 클래스 이름은 카멜케이스를 사용했기 때문에 자동으로 - 으로 변환된다
- UserControllerTest → user-controller-test
- 테스트 메서드 이름은 _ 방식으로 작성했기 때문에 그대로 만들어진다
- join_test → join_test
- 그러나 메서드를 카멜케이스로 작성했다면…
- checkUsernameAvailable_test → check-username-available_test
- 컨벤션이 망가진다
- 따라서 메서드도 테스트 코드에선 _ 를 사용하자
- check_username_available_test
5. asciidoctor 이 동작하면


- 이처럼 각각의 .adoc 파일들이

- 위 문법에 따라

- 다음과 같이 html 파일로 합쳐진다
6. 결과 화면

- 서버를 실행하고 http://localhost:8080/docs/api.html 로 접속
Share article