Guides_Creating API Documentation with Restdocs

Dev.Hammy·2023년 12월 25일
0

Spring Guides

목록 보기
32/46

이 가이드는 Spring 애플리케이션에서 HTTP 엔드포인트에 대한 문서를 생성하는 프로세스를 안내합니다.

무엇을 구축할 것인가

API를 노출하는 일부 HTTP 엔드포인트를 사용하여 간단한 Spring 애플리케이션을 구축합니다. JUnit과 Spring의 MockMvc를 사용하여 웹 계층만 테스트합니다. 그런 다음 동일한 테스트를 사용하여 Spring REST Docs를 사용하여 API에 대한 문서를 생성합니다.

Starting with Spring Initializr

간단한 애플리케이션 만들기

Spring 애플리케이션을 위한 새 컨트롤러를 만듭니다. 다음 목록(src/main/java/guides/testingrestdocs/HomeController.java)에서는 이를 수행하는 방법을 보여줍니다.

package guides.testingrestdocs;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.Map;

@RestController
public class HomeController {

    @GetMapping("/")
    public Map<String, Object> greeting() {
        return Collections.singletonMap("message", "Hello, World");
    }
}

애플리케이션 실행

Spring Initializr는 애플리케이션을 시작하는 데 사용할 수 있는 기본 클래스를 생성합니다. 다음 목록(src/main/java/guides/testingrestdocs/TestingRestdocsApplication.java)은 Spring Initializr가 생성한 애플리케이션 클래스를 보여줍니다.

package guides.testingrestdocs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestingRestdocsApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestingRestdocsApplication.class, args);
    }

}

애플리케이션 테스트

이제 애플리케이션이 실행되었으므로 테스트할 수 있습니다. http://localhost:8080에서 홈 페이지를 로드할 수 있습니다. 그러나 변경 사항을 적용할 때 애플리케이션이 작동한다는 확신을 더 가지려면 테스트를 자동화하려고 합니다. 또한 HTTP 끝점(endpoint)에 대한 설명서를 게시하려고 합니다. Spring REST Docs를 사용하여 테스트의 일부로 해당 테스트의 동적 부분을 생성할 수 있습니다.

가장 먼저 할 수 있는 일은 애플리케이션 컨텍스트를 시작할 수 없는 경우 실패하는 간단한 온전성 검사 테스트(sanity check)를 작성하는 것입니다. 이렇게 하려면 테스트 범위에서 프로젝트에 대한 종속성으로 Spring Test 및 Spring REST Docs를 추가하세요.

다음 목록은 Gradle을 사용하는 경우 추가할 항목을 보여줍니다.

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

group = 'guides'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

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'
}

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
}

tasks.named('asciidoctor') {
    inputs.dir snippetsDir
    dependsOn test
}

  1. ext { set('snippetsDir', file("build/generated-snippets")) }:

    • ext 블록은 Gradle 빌드 스크립트의 확장 속성을 정의하는 곳입니다.
    • snippetsDir이라는 이름의 확장 속성을 설정하고, 해당 디렉토리를 build/generated-snippets로 지정합니다.
  2. tasks.named('test') { outputs.dir snippetsDir useJUnitPlatform() }:

    • test task에 대한 설정입니다.
    • outputs.dir snippetsDirsnippetsDir 디렉토리가 test task의 출력 디렉토리로 지정됨을 나타냅니다.
    • useJUnitPlatform()은 JUnit 테스트를 실행하기 위해 JUnit Platform을 사용하도록 설정합니다.
  3. tasks.named('asciidoctor') { inputs.dir snippetsDir dependsOn test }:

    • asciidoctor task에 대한 설정입니다.
    • inputs.dir snippetsDirsnippetsDir 디렉토리를 asciidoctor task의 입력 디렉토리로 설정합니다.
    • dependsOn testasciidoctor task가 test task에 의존한다는 것을 나타냅니다. 즉, asciidoctor task가 실행되기 전에 test task가 먼저 실행됩니다.
  4. asciidoctor { sourceDir 'src/main/asciidoc' attributes('snippets': file('target/snippets')) }:

    • asciidoctor 블록은 Asciidoctor 플러그인의 설정을 담고 있습니다.
    • sourceDir 'src/main/asciidoc'는 Asciidoctor 플러그인이 AsciiDoc 파일을 찾을 디렉토리를 설정합니다.
    • attributes('snippets': file('target/snippets'))는 Asciidoctor 속성 중 하나로, AsciiDoc 파일에서 {snippets}를 사용할 때 이를 target/snippets 디렉토리로 치환하도록 설정합니다.

빌드 파일의 주석은 무시해도 됩니다. 이 가이드에 포함할 파일의 일부를 선택할 수 있도록 도와줍니다.

Spring MockMvc를 사용하여 HTTP 콘텐츠를 캡처하는 REST Docs의 mockmvc 버전을 포함했습니다. 자신의 애플리케이션이 Spring MVC를 사용하지 않는 경우 전체 스택 통합 테스트와 함께 작동하는 restassured 버전을 사용할 수도 있습니다.

이제 다음 예제(src/test/java/guides/testingrestdocs/TestingRestdocsApplicationTests.java)에서 볼 수 있듯이 @RunWith@SpringBootTest 주석과 빈(empty) 테스트 메서드를 사용하여 테스트 사례를 만듭니다.

package guides.testingrestdocs;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class TestingRestdocsApplicationTests {

    @Test
    void contextLoads() throws Exception{
    }

}

온전한지 확인하는 것은 좋지만 애플리케이션의 동작을 확인하는 몇 가지 테스트도 작성해야 합니다. 유용한 접근 방식은 Spring이 들어오는 HTTP 요청을 처리하고 이를 컨트롤러에 전달하는 MVC 계층만 테스트하는 것입니다. 이를 위해 Spring의 MockMvc를 사용하고 테스트 케이스의 @WebMvcTest 주석을 사용하여 주입을 요청할 수 있습니다. 다음 예제(src/test/java/guides/testingrestdocs/WebLayerTest.java)에서는 이를 수행하는 방법을 보여줍니다.

package guides.testingrestdocs;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(SpringExtension.class) //@RunWith(SpringRunner.class) JUnit 4
@WebMvcTest(HomeController.class)
public class WebLayerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string(containsString("Hello, World")));
    }
    
}

Generate Snippets for Documents 문서용 조각 생성

이전 섹션의 테스트는 HTTP 요청을 만들고(모의) 응답을 어설션합니다. 여러분이 만든 HTTP API에는 (적어도 원칙적으로는) 동적 콘텐츠가 있으므로 테스트를 감시하고 문서에 사용하기 위해 HTTP 요청을 빼낼 수 있다면 정말 좋을 것입니다. Spring REST Docs를 사용하면 "스니펫"을 생성하여 이를 수행할 수 있습니다. 테스트에 주석과 추가 "어설션"을 추가하여 이 작업을 수행할 수 있습니다. 다음 예제(src/test/java/guides/testingrestdocs/WebLayerTest.java)는 전체 테스트를 보여줍니다.

package guides.testingrestdocs;

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.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@WebMvcTest(HomeController.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class WebLayerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()).andExpect(content().string(containsString("Hello, World"))).andDo(document("home"));
    }

}

새로운 주석은 @AutoConfigureRestDocs(Spring Boot의)이며 생성된 조각의 디렉터리 위치에 대한 인수를 사용합니다. 그리고 새로운 주장은 조각의 문자열 식별자에 대한 인수를 취하는 MockMvcRestDocumentation.document입니다.

Gradle 사용자는 출력(output) 디렉터리로 target 대신 build를 사용하는 것을 선호할 수 있습니다. 그러나 그것은 중요하지 않습니다. 원하는 것을 사용하십시오.

테스트를 실행한 다음 target/snippet을 살펴보세요. 다음과 같이 Asciidoctor 조각이 포함된 home(식별자)이라는 디렉터리를 찾아야 합니다.

└── target
    └── snippets
        └── home
            └── curl-request.adoc
            └── http-request.adoc
            └── http-response.adoc
            └── httpie-request.adoc
            └── request-body.adoc
            └── response-body.adoc

기본 조각은 HTTP 요청 및 응답에 대한 Asciidoctor 형식입니다. 또한 curlhttpie(두 가지 일반적이고 널리 사용되는 명령줄 HTTP 클라이언트)에 대한 명령줄 예제도 있습니다.

테스트에서 document() 어설션에 인수를 추가하여 추가 조각을 생성할 수 있습니다. 예를 들어 다음 예제(src/test/java/guides/testingrestdocs/WebLayerTest.java에서)에 표시된 것처럼 PayloadDocumentation.responseFields() 조각을 사용하여 JSON 응답의 각 필드를 문서화할 수 있습니다.

this.mockMvc.perform(get("/"))
    ...
    .andDo(document("home", responseFields(
        fieldWithPath("message").description("The welcome message for the user.")
    ));

테스트를 실행하면 response-fields.adoc라는 추가 조각 파일을 찾아야 합니다. 여기에는 필드 이름과 설명이 포함된 테이블이 포함되어 있습니다. 필드를 생략하거나 이름이 잘못되면 테스트가 실패합니다. 이것이 REST Docs의 힘입니다.

사용자 정의 스니펫을 생성하고 스니펫의 형식을 변경하며 호스트 이름과 같은 값을 사용자 정의할 수 있습니다. 자세한 내용은 Spring REST Docs 문서를 참조하세요.

스니펫 사용

생성된 코드 조각을 사용하려면 프로젝트에 Asciidoctor 콘텐츠를 일부 포함하고 빌드 시 코드 조각을 포함해야 합니다. 이 작업을 보려면 src/main/asciidoc/index.adoc라는 새 파일을 만들고 원하는 대로 스니펫을 포함하세요. 다음 예제(src/main/asciidoc/index.adoc)에서는 이를 수행하는 방법을 보여줍니다.

= Getting Started With Spring REST Docs

This is an example output for a service running at http://localhost:8080:

.request
include::{snippets}/home/http-request.adoc[]

.response
include::{snippets}/home/http-response.adoc[]

As you can see the format is very simple, and in fact you always get the same message.

이 Asciidoc 파일의 주요 특징은 Asciidoctor include 지시문(directive)을 사용하여 두 개의 스니펫을 포함한다는 것입니다(콜론과 후행 괄호는 파서에게 해당 행에 대해 특별한 작업을 수행하도록 지시합니다). 포함된 조각에 대한 경로는 {snippets}라는 자리 표시자(Asciidoctor의 attribute)로 표현됩니다. 이 간단한 경우에 유일한 다른 마크업은 상단의 =(레벨 1 섹션 제목)와 스니펫의 캡션('요청' 및 '응답') 앞에.입니다..는 해당 줄의 텍스트를 캡션으로 바꿉니다.

그런 다음 빌드 구성에서 이 소스 파일을 선택한 문서 형식으로 처리해야 합니다.

Gradle을 사용하는 경우 ./gradlew asciidoctor를 실행하면 build/asciidoc이 생성됩니다. 다음 목록은 build.gradle 파일의 Asciidoctor 관련 부분을 보여줍니다.


index.adoc의 include 문에서 {snippets}build.gradle에 의해 build/generated-snippets로 지정되고 있었습니다.

// 중략
ext {
    set('snippetsDir', file("build/generated-snippets"))
}

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
}

tasks.named('asciidoctor') {
    inputs.dir snippetsDir
    dependsOn test
}

build 설정에 따르면 test 태스크의 출력물은 snippetsDir로 향하고, asciidoctor 태스크의 입력물은 snippetsDir로부터 옵니다. 그리고 이 태스크는 test 태스크를 실행한 후 수행됩니다.

그러나 튜토리얼 원문에서 @AutoConfigureRestDocsoutputDir의 인수를 target/snippets로 지정하는 바람에 build/generated-snippets 폴더로 .adoc 파일들이 모이지 못했고, 이 때문에 src/main/asciidoc/index.adocinclude 지시문은 제대로 작동하지 않고 있었습니다.

결국 @AutoConfigureRestDocsoutputDir의 인수를 build/generated-snippets로 수정한 후에야 제대로 된 결과를 볼 수 있었습니다.


0개의 댓글