일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- GIT
- 자료구조와 함께 배우는 알고리즘 입문
- 자바편
- 처음 만나는 AI 수학 with Python
- 이터레이터
- 구멍가게코딩단
- resttemplate
- 코드로배우는스프링부트웹프로젝트
- 페이징
- 데비안
- d
- 네트워크 설정
- baeldung
- /etc/network/interfaces
- network configuration
- iterator
- 목록처리
- 자료구조와함께배우는알고리즘입문
- 알파회계
- 친절한SQL튜닝
- Kernighan의 C언어 프로그래밍
- 스프링 시큐리티
- 선형대수
- ㅒ
- 처음 만나는 AI수학 with Python
- 서버설정
- 티스토리 쿠키 삭제
- 코드로배우는스프링웹프로젝트
- 리눅스
- 스프링부트핵심가이드
- Today
- Total
bright jazz music
blog13: Spring REST Docs 1 -기본설정 본문
API를 설명하기 위한 문서 작성 라이브러리는 스웨거를 포함하여 다수이다. 여기서는 Spring REST docs를 사용한다.
https://spring.io/projects/spring-restdocs
Spring REST Doc의 장점
- 운영 코드에 영향이 없다.
다른 라이브러리는 운영 중인 코드에 어노테이션 또는 xml을 사용해 영향을 가해서 문서를 만들어 내는 반면, Spring REST Docs는 운영코드를 수정하지 않고도 단순히 테스트 케이스만을 사용하여 문서를 만들어낼 수 있다.
- 변경된 기능에 대해 최신문서 유지가 어느 정도 가능하다.
(어떤 라이브러리는 코드가 수정되어도 문서가 수정되지 않는다.) Spring REST Docs는 테스트 케이스를 실행하여 테스트가 성공했을 때 문서를 자동으로 생성해 준다. 즉, 개발자가 기능을 추가 또는 변경한 후, 그에 대한 테스트 케이스를 만들어 줌으로써 스프링 레스트 독스가 자동으로 최신버전을 유지하도록 할 수 있다는 뜻이다.
-- 설정 시작 --
공식 페이지 - Learn - Reference Page
1. build.gradle에 설정 추가
/*build.gradle*/
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.6'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
//SpringRestDoc
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
group = 'com.endofma'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
//SpringRestDoc
asciidoctorExt
}
repositories {
mavenCentral()
}
ext { //변수를 선언할 것이므로 파일 상부에 적어주는 것이 좋다.
//SpringRestDoc
snippetsDir = file('build/generated-snippets') //생성된 asciidoc 위치 지정
asciidocVersion = "3.0.0"
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' /*추가*/
//gradle 5버전부터 annotationprocessor가 사용 가능해서
// gradle 레벨에서 컴파일 할 때 필요한 querydsl클래스 파일을 생성할 수 있게 됐다.
//querydsl 관련 디펜던시
implementation 'com.querydsl:querydsl-core'
implementation 'com.querydsl:querydsl-jpa'
//annotationProcessor : 인식할 타입을 추가함
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//SpringRestDoc
asciidoctorExt "org.springframework.restdocs:spring-restdocs-asciidoctor:${asciidocVersion}"
testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc:${asciidocVersion}"
}
test {
//SpringRestDoc
outputs.dir snippetsDir
}
//SpringRestDoc
asciidoctor {
inputs.dir snippetsDir
configurations 'asciidoctorExt'
dependsOn test //테스트를 먼저 수행한다
}
//SpringRestDoc
//나중에 프로젝트를 jar로 빌드할 때 ascciidoc도 포함될 수 있도록 한다.
bootJar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}
tasks.named('test') {
useJUnitPlatform()
}
추가해준 뒤 gradle sync 해주는 것을 잊지 말 것.
2. Document Snippet 생성하기
테스트를 통해 asciidoc 파일을 생성하자.
JUnit5를 사용하는 경우, RestDocumentExtension을 적용하기 위해 클래스를 생성해 줘야 한다.
//PostControllerDocTest.java
package com.endofma.blog.controller;
import com.endofma.blog.domain.Post;
import com.endofma.blog.repository.PostRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
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.status;
@SpringBootTest //부트의 경우 이걸 사용. 이 안에 SpringExtension이 포함되어 있다.
@ExtendWith(RestDocumentationExtension.class) //부트의 경우
//@ExtendWith({RestDocumentationExtension, SpringExtension.class}) //Spring MVC의 경우는 이렇게
public class PostControllerDocTest {
//테스트 수행 전 셋업
private MockMvc mockMvc;
@Autowired
private PostRepository postRepository;
//JUnit자체적으로 주입하기 때문에 생성자를 통해 주입하면 아래 에러 발생
//org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter
//public PostControllerDocTest(PostRepository postRepository){
// this.postRepository = postRepository;
//}
//asciidoc에 맞는 설정을 가지고 mockMvc를 설정하는 코드
@BeforeEach
void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.build();
}
@Test
@DisplayName("글 단건 조회 테스트")
void test1() throws Exception {
//given
Post post = Post.builder()
.title("제목")
.content("내용")
.build();
postRepository.save(post);
//expected
// this.mockMvc.perform(get("/")
this.mockMvc.perform(get("/posts/{postId}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andDo(document("index"));
}
}
/*
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.6)
2023-01-15 16:01:47.084 INFO 40064 --- [ Test worker] c.e.b.controller.PostControllerDocTest : Starting PostControllerDocTest using Java 11.0.12 on DESKTOP-Q7HBM41 with PID 40064 (started by user in D:\personal\blog)
2023-01-15 16:01:47.086 INFO 40064 --- [ Test worker] c.e.b.controller.PostControllerDocTest : No active profile set, falling back to 1 default profile: "default"
2023-01-15 16:01:48.174 INFO 40064 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-01-15 16:01:48.255 INFO 40064 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 70 ms. Found 1 JPA repository interfaces.
2023-01-15 16:01:48.938 INFO 40064 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2023-01-15 16:01:49.268 INFO 40064 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2023-01-15 16:01:49.383 INFO 40064 --- [ Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-01-15 16:01:49.467 INFO 40064 --- [ Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.14.Final
2023-01-15 16:01:49.772 INFO 40064 --- [ Test worker] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2023-01-15 16:01:49.974 INFO 40064 --- [ Test worker] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2023-01-15 16:01:50.830 INFO 40064 --- [ Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-01-15 16:01:50.841 INFO 40064 --- [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-01-15 16:01:51.752 WARN 40064 --- [ Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-01-15 16:01:52.199 INFO 40064 --- [ Test worker] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:blog'
2023-01-15 16:01:52.704 INFO 40064 --- [ Test worker] c.e.b.controller.PostControllerDocTest : Started PostControllerDocTest in 6.09 seconds (JVM running for 8.91)
2023-01-15 16:01:52.950 INFO 40064 --- [ Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2023-01-15 16:01:52.950 INFO 40064 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2023-01-15 16:01:52.952 INFO 40064 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 2 ms
MockHttpServletRequest:
HTTP Method = GET
Request URI = /posts/1
Parameters = {}
Headers = [Accept:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.endofma.blog.controller.PostController
Method = com.endofma.blog.controller.PostController#get(Long)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {"id":1,"title":"제목","content":"내용"}
Forwarded URL = null
Redirected URL = null
Cookies = []
2023-01-15 16:01:53.364 INFO 40064 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-01-15 16:01:53.364 INFO 40064 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2023-01-15 16:01:53.369 WARN 40064 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 90121, SQLState: 90121
2023-01-15 16:01:53.370 ERROR 40064 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper : Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-214]
2023-01-15 16:01:53.371 WARN 40064 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 90121, SQLState: 90121
2023-01-15 16:01:53.371 ERROR 40064 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper : Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-214]
2023-01-15 16:01:53.371 WARN 40064 --- [ionShutdownHook] o.s.b.f.support.DisposableBeanAdapter : Invocation of destroy method failed on bean with name 'entityManagerFactory': org.hibernate.exception.JDBCConnectionException: Unable to release JDBC Connection used for DDL execution
2023-01-15 16:01:53.372 INFO 40064 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2023-01-15 16:01:53.377 INFO 40064 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 12s
4 actionable tasks: 2 executed, 2 up-to-date
PM 4:01:53: Execution finished ':test --tests "com.endofma.blog.controller.PostControllerDocTest.test1"'.
*/
테스트에 성공하면 아래와 같은 파일들이 생성된다.
build.gradle에서 아래의 경로로 설정해주었던 것을 기억하자.
snippetsDir = file('build/generated-snippets') //생성된 asciidoc 위치 지정
3. 생성된 스니펫들을 HTML파일로 보여주기
우선은 .adoc 파일을 하나 생성해야 한다.
// index.adoc
//{snippetes}: /build/generated-snippets
include::{snippets}/index/http-request.adoc[]
include::{snippets}/index/http-response.adoc[]
include::{snippets}/index/curl-request.adoc[]
// include::{snippets}/index/httpie-request.adoc[]
// include::{snippets}/index/request-body.adoc[]
// include::{snippets}/index/response-body.adoc[]
html5 파일이 생성될 경로를 만들어준다. 현재는 static/docs가 아니라 다른 데 생성되고 있어서 build.gradle에 println을 통해서 생성되는 경로를 알아보았다.
//SpringRestDoc
//나중에 프로젝트를 jar로 빌드할 때 ascciidoc도 포함될 수 있도록 한다.
bootJar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}") {
println " >>> " + asciidoctor.outputDir //경로 확인하기 위해 수정해 줬음
into 'static/docs'
}
}
//> Configure project :
// >>> D:\personal\blog\build\docs\asciidoc
이걸 /src/main/resources/docs 디렉토리에 옮겨오기 위해 아래처럼 수정해 주었다.
이제 재빌드 한다.
*(gradle - build -bootjar만 실행해 주어도 된다.bootjar만 수정했기 때문이다.)
//SpringRestDoc
//나중에 프로젝트를 jar로 빌드할 때 ascciidoc도 포함될 수 있도록 한다.
bootJar {
dependsOn asciidoctor
copy {
from asciidoctor.outputDir
into "src/main/resources/static/docs"
}
// from ("${asciidoctor.outputDir}/html5") {
// from ("${asciidoctor.outputDir}") {
// println " >>> " + asciidoctor.outputDir
// into 'static/docs'
// }
}
프로젝트 빌드
> Task :test // 먼저 테스트
...
> Task :asciidoctor //그 다음 아스키독 생성
> Task :bootJarMainClassName
> Task :bootJar
> Task :assemble
> Task :check
> Task :build
4. 생성된 HTML 확인
프로젝트를 구동하자.
브라우저로 확인.
// index.adoc
//{snippetes}: /build/generated-snippets
= 블로그 API
:toc:
== 글 단건 조회
=== 요청
include::{snippets}/index/http-request.adoc[]
=== 응답
include::{snippets}/index/http-response.adoc[]
=== curl
include::{snippets}/index/curl-request.adoc[]
// include::{snippets}/index/httpie-request.adoc[]
// include::{snippets}/index/request-body.adoc[]
// include::{snippets}/index/response-body.adoc[]
1. gradle.build 재빌드 또는 gradle - build - bootjar 실행
2. 서버 재시작
3. 브라우저에서 localhost:8080/docs/index.html 접근
'Projects > blog' 카테고리의 다른 글
blog13: Spring REST Docs3 - 커스터마이징 (0) | 2023.01.29 |
---|---|
blog13: Spring REST Docs2 - 요청, 응답필드 (0) | 2023.01.16 |
blog12: 예외처리 4 (0) | 2023.01.14 |
blog12: 예외처리 3 (0) | 2023.01.14 |
blog12: 예외처리2 (0) | 2023.01.13 |