DevLog ๐Ÿ˜ถ

[Spring] Rest-docs ์—ฐ๋™ํ•˜๊ธฐ (version 3.3.2) ๋ณธ๋ฌธ

Back-end/Spring

[Spring] Rest-docs ์—ฐ๋™ํ•˜๊ธฐ (version 3.3.2)

dolmeng2 2023. 5. 22. 13:56

๐ŸŒฑ ๋“ค์–ด๊ฐ€๊ธฐ ์ „

์‚ฌ์‹ค rest-docs๋Š” ๋ฏธ์…˜ ์š”๊ตฌ์‚ฌํ•ญ์€ ์•„๋‹ˆ์—ˆ์ง€๋งŒ, ๊ฐœ์ธ์ ์ธ ์š•์‹ฌ์œผ๋กœ ํ•œ ๋ฒˆ ์ง์ ‘ ๊ตฌ์ถ•ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์–ด์„œ ๋ฏธ์…˜ํ•˜๋Š” ๊น€์— ํ•จ๊ป˜ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. (์–ด์ฐจํ”ผ ์ถ”ํ›„ ๋ฏธ์…˜ ์ง„ํ–‰ํ•˜๋ฉด์„œ ํ”„๋ก ํŠธ ํฌ๋ฃจ์™€ ํ˜‘์—…ํ•˜๊ฒŒ ๋˜๋ฉด ์„ธํŒ…ํ•ด์•ผ ํ•˜๋‹ˆ๊นŒ...)

RestDocs๋ฅผ ์—ฐ๋™ํ•  ๋•Œ ์ธ์ˆ˜ ํ…Œ์ŠคํŠธ ๋ ˆ๋ฒจ์—์„œ ์ง„ํ–‰ํ• ์ง€ (RestAssured) ์•„๋‹ˆ๋ฉด ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ ์ง„ํ–‰ํ• ์ง€ ๊ณ ๋ฏผํ–ˆ์—ˆ๋Š”๋ฐ, RestAssured๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด BDD ์Šคํƒ€์ผ์ด๋ผ ๋” ์ง๊ด€์ ์ผ ๊ฒƒ ๊ฐ™๊ธด ํ•˜์ง€๋งŒ ์•„๋ฌด๋ž˜๋„ ์†๋„๊ฐ€ ๋Š๋ฆฌ๋‹ค ๋ณด๋‹ˆ๊นŒ ๊ทธ๋ƒฅ ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ์ด์–ด์—์„œ ์ง„ํ–‰ํ–ˆ๋‹ค. (์‹ค์ œ๋กœ๋„ ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ๋ฒจ์—์„œ ๋งŽ์ด ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.) 

 


 

โœ”๏ธ Rest-docs๊ฐ€ ์–ด๋–ค ๊ฑฐ์ง€?

Spring Rest Docs๋Š” Restful ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ •ํ™•ํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฌ์šด ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๋„์›€์„ ์ฃผ๋Š” ๋ฌธ์„œ ์ž๋™ํ™” ๋„๊ตฌ์ด๋‹ค.

ํ”ํžˆ ๋ฐฑ์—”๋“œ, ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ํ˜‘์—…ํ•  ๋•Œ ํ”„๋ก ํŠธ์—”๋“œ๋Š” API endPoint๋ฅผ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•ด๋‹น API๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์„œ response๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋งŒ์•ฝ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ API ๋ฌธ์„œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค๋ฉด ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์žํ•œํ…Œ ์–ด๋–ป๊ฒŒ ์•Œ๋ ค์•ผ ํ• ๊นŒ?

์ง์ ‘ ๋ฉ”์‹ ์ €๋กœ ๐Ÿ™‡‍โ™€๏ธ 'oo๋‹˜, xxx endpoint์˜ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์–ด์š”' ๋ผ๊ณ  ๋งํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ, ์ด๋Š” ๋งค์šฐ ๋ฒˆ๊ฑฐ๋กญ๋‹ค.

๋งŒ์•ฝ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋‹น์žฅ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š” hotfix ์š”๊ตฌ์‚ฌํ•ญ์ž„์—๋„ ๋ฉ”์‹ ์ €๋ฅผ ์ฝ์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด? ํ˜น์€, ์›ํ•˜๋Š” ์‹œ๊ฐ„์— ์„œ๋กœ ์†Œํ†ตํ•  ์ˆ˜ ์—†๋‹ค๋ฉด? ํ•˜๋‚˜์˜ ์ •ํ˜•ํ™”๋œ ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒŒ ํ›จ์”ฌ ์‰ฌ์šธ ๊ฒƒ์ด๋‹ค.

 

Swagger๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ๊ณ , Rest docs๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์„ ํ…๋ฐ ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ์ด ๋šœ๋ ทํ•˜๋‹ค.

 

https://jojoldu.tistory.com/31

 

Swagger์˜ ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์— ๋ถ€๋‹ด์ด ์—†์ง€๋งŒ, ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ ์ž์ฒด์— ๋ฌธ์„œ์— ๋Œ€ํ•œ ์„ค์ • ๋‚ด์šฉ์ด ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ž์ฒด๊ฐ€ ๋ถ€๋‹ด์ด ๋  ์ˆ˜ ์žˆ๋‹ค. Swagger์—์„œ Rest docs๋กœ ์ „ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ ์ž์ฒด๋ฅผ ์ „๋ถ€ ๋ฐ”๊ฟ”์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํ•˜์ง€๋งŒ, Rest-docs๋ณด๋‹ค๋Š” ๋น„๊ต์  ์ ์šฉํ•˜๊ธฐ๊ฐ€ ์‰ฌ์šฐ๋ฉฐ, ๋ฌธ์„œ ์ž์ฒด๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๊ตฌ์ถ•ํ•  ํ•„์š”์—†์ด ์•Œ์•„์„œ ๋งŒ๋“ค์–ด์ฃผ๊ณ , ์ง์ ‘ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ์ž…์žฅ์—์„œ๋Š” ์กฐ๊ธˆ ๋” ํŽธํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

๋ฐ˜๋ฉด์— Rest-docs์˜ ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์—๋Š” ์˜ํ–ฅ์„ ๋ผ์น˜์ง€ ์•Š๋Š”๋‹ค.

์ด ๋ง์€ ๊ณง, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ •๋ง ๊ผผ๊ผผํ•˜๊ฒŒ ์งœ์—ฌ ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์„ฑ๊ณตํ–ˆ์„ ๋•Œ, ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋“ฑ๋“ฑ... response ๊ฐ’์ด ๋ณ€ํ™”ํ•˜์—ฌ ๋‚ด๋ ค๊ฐ€๋Š” ๊ฒฝ์šฐ์—๋Š” ์›ฌ๋งŒํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. (๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— RestAssured๋ณด๋‹ค ๋œ ๋ฌด๊ฑฐ์šด MockMvc๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒŒ ๋” ๋‚ซ๋‹ค๊ณ  ํŒ๋‹จํ•œ ์ ๋„ ์žˆ๋‹ค.)

๋˜ํ•œ, ์Šค์›จ๊ฑฐ์— ๋น„ํ•ด ๊ตฌ์ถ•์ด ์–ด๋ ค์šด ํŽธ์ด๋‹ค. ๋ฌธ์„œ ๊ตฌ์ถ• ์ž์ฒด๋Š” ๋นŒ๋“œ๋œ adoc ํŒŒ์ผ์„ ์ง์ ‘ ์กฐ๋ฆฝํ•ด์•ผ ํ•˜๊ณ , ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜๋„ ์—†๋‹ค. ํ•˜์ง€๋งŒ, ์ง์ ‘ ๊ตฌ์ถ•ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์„œ ๋‚ด๋ถ€์— ์–ด๋–ค ๋‚ด์šฉ์ด ๋“ค์–ด๊ฐˆ์ง€ ์กฐ๊ธˆ ๋” ์„ธ๋ถ„ํ™”ํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ฐœ์ธ์ ์œผ๋กœ ๊ทœ๋ชจ๊ฐ€ ์ž‘๋‹ค๋ฉด swagger๋ฅผ ์‚ฌ์šฉํ•ด๋ณผ๋งŒ ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ, ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ ์ž์ฒด๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ฒŒ ์ฐ์ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— rest-docs๋ฅผ ์กฐ๊ธˆ ๋” ์„ ํ˜ธํ•˜๋Š” ํŽธ์ด๋‹ค. (๋ฌผ๋ก  ๊ทธ๋•Œ๋ถ€ํ„ฐ ํ…Œ์ŠคํŠธ ์ง€์˜ฅ์ด์ง€๋งŒ... ใ…Ž_ใ…Ž) ํ•œ ๋ฒˆ ์ ์šฉํ•ด๋‘๋ฉด ๊ทธ๋’ค๋ถ€ํ„ฐ ์‘์šฉ์€ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์— ์ฒ˜์Œ ๋Ÿฌ๋‹์ปค๋ธŒ๋งŒ ์กฐ๊ธˆ ๊ฒฌ๋””๋ฉด ๋œ๋‹ค!

 


 

โœ”๏ธ SetUp - build.gradle

๐ŸŒฑ ๊ทธ์ „์—, Rest Docs์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ์ ์œผ๋กœ 'Asciidoctor'๋ผ๋Š” ์นœ๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด ์นœ๊ตฌ๋Š” ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ HTML ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์นœ๊ตฌ๋‹ค. ์ด ์นœ๊ตฌ์˜ ๊ฐœ๋… ์ž์ฒด๊ฐ€ ์ค‘์š”ํ•œ ๊ฑด ์•„๋‹ˆ์ง€๋งŒ, HTML๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์ผ์ข…์˜ ์ „์ฒ˜๋ฆฌ ๋„๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜์ž.

๐ŸŒฑ ๋˜ํ•œ, ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด 'snippet'์ด๋ผ๋Š” ์นœ๊ตฌ๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๋ฐ, ์ด๋Ÿฌํ•œ ์Šค๋‹ˆํŽซ๋“ค์„ ์กฐ๋ฆฝํ•˜์—ฌ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ž.

 

๋จผ์ €, build.gradle์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

plugins {
    id "org.asciidoctor.jvm.convert" version "3.3.2" 
}

configurations {
    asciidoctorExt 
}

ext {
    snippetsDir = file('build/generated-snippets') 
}

test {
    outputs.dir snippetsDir 
}

asciidoctor {
    inputs.dir snippetsDir 
    configurations 'asciidoctorExt' 
    dependsOn test 
    
    sources {
        include("**/index.adoc") 
    }

    baseDirFollowsSourceFile() 
}

asciidoctor.doFirst {
    delete file('src/main/resources/templates/docs') 
}

task copyDocument(type: Copy) { 
    dependsOn asciidoctor
    from file("build/docs/asciidoc")
    into file("src/main/resources/templates/docs")
}

build {
    dependsOn copyDocument 
}

dependencies {
    asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' 
}

bootJar {
    dependsOn asciidoctor 
    from ("${asciidoctor.outputDir}/html5") { 
        into 'static/docs'
    }
}

์ƒ๊ฐ๋ณด๋‹ค ์„ค์ •ํ•ด์ค„ ๊ฒƒ์ด ๊ต‰์žฅํžˆ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ธˆ์”ฉ ๋‚˜๋ˆ ์„œ ๋ณด๋„๋ก ํ•˜์ž.

 

plugins {
    id "org.asciidoctor.jvm.convert" version "3.3.2" (1)
}

configurations {
    asciidoctorExt (2) 
}

ext {
    snippetsDir = file('build/generated-snippets') (3)
}

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

(1): Asciidoctor ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ ์šฉํ•œ๋‹ค. 

 

(2): asciidoctorExt๋ฅผ ํ”„๋กœ์ ํŠธ์˜ configuration์— ์ถ”๊ฐ€ํ•œ๋‹ค.

asciidoctorExt๋Š” Asciidoctor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํฌ๋งทํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์ธ๋ฐ, ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ HTML ํ˜•์‹์œผ๋กœ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

 

(3): ์ƒ์„ฑ๋œ ์Šค๋‹ˆํŽซ๋“ค์„ ์–ด๋Š ์œ„์น˜์— ์ €์žฅํ•  ๊ฒƒ์ธ์ง€ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. (๋””ํดํŠธ๋กœ build ํด๋” ๋ฐ‘์— ์ƒ์„ฑํ•˜๋Š” ํŽธ)

 

(4): ์ €์žฅํ•œ ์Šค๋‹ˆํŽซ ๊ฒฝ๋กœ๊ฐ€ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ถ”๊ฐ€๋˜๋„๋ก ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์Šค๋‹ˆํŽซ์ด ์ƒ์„ฑ๋˜๋ฉด, ํ•ด๋‹น ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ €์žฅ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

asciidoctor { 
    inputs.dir snippetsDir (5)
    configurations 'asciidoctorExt' (6)
    dependsOn test (7)
    
    sources { 
        include("**/index.adoc") (8)
    }

    baseDirFollowsSourceFile() (9)
}

asciidoctor.doFirst {
    delete file('src/main/resources/templates/docs') (10)
}

(5) asciidoctor๊ฐ€ ํ•  ์ผ์„ ์ •์˜ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.

์ด์ „์— ์„ค์ •ํ•œ ์Šค๋‹ˆํŽซ ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅ ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ๋งŒ๋“ค์–ด์„œ, asciidoctor๊ฐ€ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์–ด๋””๋ฅผ ์ฐธ์กฐํ• ์ง€ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

(6) ext์—์„œ ๊ตฌ์„ฑํ•œ asciidoctorExt๋ฅผ ๊ตฌ์„ฑ ํŒŒ์ผ๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค.

(asciidoctor์˜ configuration์„ ์„ค์ •ํ•  ๊ฑด๋ฐ, ์–ด๋–ค ์„ค์ • ์ •๋ณด๋ฅผ ์ฐธ์กฐํ•  ๊ฒƒ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋Š๋‚Œ?)

 

(7) ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก, test์— ์ข…์†์ ์ด๋„๋ก ๋งŒ๋“ ๋‹ค. (test ์‹คํ–‰ -> ๋ฌธ์„œ ์ƒ์„ฑ ๋Š๋‚Œ!)

 

(8) rest-docs ๋ฌธ์„œ์˜ ๊ฒฝ์šฐ, .adoc ํŒŒ์ผ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ƒ์„ฑํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋•Œ source๋ฅผ ์ง€์ •ํ•˜๊ฒŒ ๋˜๋ฉด ํŠน์ • adoc๋งŒ HTML๋กœ ๋งŒ๋“ ๋‹ค.

๋งŒ์•ฝ ๋ชจ๋“  adoc์— ๋Œ€ํ•ด ๊ฐ๊ฐ html์„ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด ํ•ด๋‹น ์˜ต์…˜์€ ์ œ๊ฑฐํ•ด๋„ ๋œ๋‹ค.

 

(9) ํŠน์ • .adoc์— ๋‹ค๋ฅธ adoc์„ include ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ๊ฒฝ๋กœ๋ฅผ baseDir๋กœ ๋งž์ถฐ์ฃผ๋Š” ์—ญํ• ์ด๋‹ค.

๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ index.adoc์œผ๋กœ ์„ธ๋ถ€ adoc์„ ๋กœ๋“œํ•˜๋„๋ก ๋งŒ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ์˜ต์…˜์„ ํ™œ์„ฑํ™”ํ•ด์ฃผ์—ˆ๋‹ค.

 

(10) static.docs ํŒŒ์ผ์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ๋งŒ๋“ค๋„๋ก ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” static ํ•˜์œ„์— ๋‘๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ, ๋ฏธ์…˜์—์„œ ํƒ€์ž„๋ฆฌํ”„๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋‹ˆ templates ํด๋” ํ•˜์œ„๋กœ ์„ค์ •ํ•˜์˜€๋‹ค.

 

task copyDocument(type: Copy) { (11)
    dependsOn asciidoctor
    from file("build/docs/asciidoc")
    into file("src/main/resources/templates/docs")
}

build {
    dependsOn copyDocument (12)
}

dependencies { 
    asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' (13)
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' (14)
}

bootJar { 
    dependsOn asciidoctor (15)
    from ("${asciidoctor.outputDir}/html5") { 
        into 'templates/docs'
    }
}

 

(11) asciidoctor ์ž‘์—…ํ•œ ์ดํ›„ (dependsOn), build/docs/asciidoc์„ ๋ณด๋ฉด ์ƒ๋‹จ์—์„œ ์„ค์ •ํ•ด์ค€ source ์˜ต์…˜์— ๋”ฐ๋ผ html์ด ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค. ๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์—์„œ html์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ resources/templates/docs์— ๋ณต์‚ฌ๋ฅผ ์ง„ํ–‰ํ•˜์˜€๋‹ค.

 

(12) ๋นŒ๋“œ ์ž‘์—…์ด copyDocument ์ดํ›„์— ์ง„ํ–‰๋˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.

 

(13) asciidoctorExt configuration์„ ์‚ฌ์šฉํ•˜์—ฌ restdocs-asciidoctor ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ์˜์กด์„ฑ์„ ์„ค์ •ํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด .adoc ํŒŒ์ผ์—์„œ ๋นŒ๋“œ, ์ƒ์„ฑ๋œ ์Šค๋‹ˆํŽซ์„ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ์Šค๋‹ˆํŽซ ์†์„ฑ์ด ์ž๋™์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

 

(14) MockMVC๋ฅผ ํ™œ์šฉํ•˜์—ฌ rest-docs๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. (Rest-Assured๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด spring-restdocs-restassured ์‚ฌ์šฉ)

 

(15) ์ƒ์„ฑ๋œ ๋ฌธ์„œ๋ฅผ jar ํŒŒ์ผ์— ํŒจํ‚ค์ง•ํ•ด์„œ ์ •์  ์ปจํ…์ธ ๋กœ ์ œ๊ณตํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. 

asciidoctor์— ์ข…์†์ ์ด๋„๋ก ๋งŒ๋“ค์–ด์„œ, jar ํŒŒ์ผ์„ ๋นŒ๋“œํ•˜๊ธฐ ์ „์— ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด jar ํŒŒ์ผ์ด ๋นŒ๋“œ๋˜๊ธฐ ์ „์— ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ์ƒ์„ฑ๋œ ๋ฌธ์„œ๋Š” jar ํŒŒ์ผ์— ํฌํ•จ๋œ๋‹ค. 

asciidoctor ์ง„ํ–‰ ์ดํ›„ ๋ฌธ์„œ๋ฅผ templates/docs๋กœ ์ด๋™์‹œํ‚จ๋‹ค.

 

๐ŸŒฑ ๊ทธ๋ž˜์„œ ๊ฒฐ๊ณผ์ ์œผ๋กœ ./gradlew clean build๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด test -> asciidoctor -> copyDocument -> build ์ˆœ์œผ๋กœ ์„ธํŒ…์ด ์ง„ํ–‰๋œ๋‹ค.


 

โœ”๏ธ ์ฝ”๋“œ์— ์ ์šฉํ•˜๊ธฐ (Junit5 ๊ธฐ์ค€)

์šฐ์„ , ํ…Œ์ŠคํŠธํ•  ๋Œ€์ƒ ํด๋ž˜์Šค์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์…‹์—… ์ž‘์—…์„ ์ง„ํ–‰ํ•ด์ค€๋‹ค.

controller๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉํ•  ๋ถ€๋ถ„์€ ๋นผ๋Š” ๊ฒƒ์ด ์ข‹๊ธฐ ๋•Œ๋ฌธ์—, ๋‚˜๋Š” ๋ณ„๋„์˜ helper ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด๋‘์—ˆ๋‹ค.

@ExtendWith(RestDocumentationExtension.class) (1)
public class RestDocsHelper {

    private MockMvc mockMvc;

    private RestDocumentationResultHandler documentationResultHandler;

    @BeforeEach
    void setUp(final WebApplicationContext webApplicationContext,
               final RestDocumentationContextProvider restDocumentationContextProvider) {
  
        this.documentationResultHandler = MockMvcRestDocumentation.document( 
                "{class-name}/{method-name}", (2)
                Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), (3)
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()));

        this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) (4)
                .addFilter(new CharacterEncodingFilter("UTF-8", true)) (5)
                .alwaysDo(MockMvcResultHandlers.print()) (6)
                .alwaysDo(documentationResultHandler) (7)
                .apply(documentationConfiguration(restDocumentationContextProvider)) (8)
                .build();
    }
}

(1): Junit5์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Spring Rest Docs์˜ RestDocumentationExtension ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ํ™•์žฅ์‹œํ‚ค๋Š” ๊ฒƒ์ด๋‹ค. ์–ด๋ ต๊ฒŒ ์ƒ๊ฐํ•  ํ•„์š”์—†์ด, API ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ํ™˜๊ฒฝ์„ ์„ค์ •ํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

(2): ๋ฌธ์„œ์˜ ์„ธ๋ถ€ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค. build/generated-snippets ํŒจํ‚ค์ง€ ๋ฐ‘์—, class-name ํŒจํ‚ค์ง€, ๊ทธ๋ฆฌ๊ณ   method-name ํŒจํ‚ค์ง€ ํ•˜๋‹จ์— ๊ฐ๊ฐ์˜ ์Šค๋‹ˆํŽซ์ด ์ƒ์„ฑ๋˜๋„๋ก ๋งŒ๋“œ๋Š” ์„ค์ •์ด๋‹ค.

- ์‚ฌ์‹ค ์ด ๋ถ€๋ถ„์— ๋‚˜์ค‘์— ๋ฐฐํฌ๋  ๋•Œ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ํ”„๋กœํ† ์ฝœ ์ •๋ณด๋‚˜ host ์ •๋ณด๋„ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. (Preprocessors.modifyUris ํ™œ์šฉ)

 

(3) request, response์— ๋Œ€ํ•ด์„œ ์ „์ฒ˜๋ฆฌ ์ž‘์—…์„ ํ•ด์ฃผ๋Š” ๊ฒƒ์ธ๋ฐ ์š”์ฒญ, ์‘๋‹ต์— ๋Œ€ํ•ด ์ฝ๊ธฐ ์‰ฝ๋„๋ก ๋งŒ๋“ค์–ด์ค€๋‹ค.

 

(4): ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ๊ณตํ†ต์ ์œผ๋กœ ์‹คํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•œ๋‹ค. 

mockMvc์— ๋Œ€ํ•ด์„œ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ ํ†ตํ•ด Spring REST docs์—์„œ ์ƒ์„ฑ๋œ ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์ฃผ๋Š” ์ „์ดˆ ์ž‘์—…์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

(5) ๋ฌธ์„œ ์ƒ์„ฑ ์‹œ ํ•œ๊ธ€์ด ๊นจ์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์œ„ํ•ด ์ธ์ฝ”๋”ฉ ํ˜•์‹์„ ์ง€์ •ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค.

 

(6) ์ฒ˜๋ฆฌํ•œ ๋‚ด์šฉ์— ๋Œ€ํ•ด ํ•ญ์ƒ ๋กœ๊น… ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก print๋ฅผ ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค.

 

(7) ์œ„์—์„œ ์„ค์ •ํ•œ ์ •๋ณด๋“ค์„ ํ•ญ์ƒ ์ ์šฉํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค.

 


 

์ƒ๊ฐ๋ณด๋‹ค ๊ธ€์ด ๊ธธ์–ด์งˆ ๊ฒƒ ๊ฐ™์•„์„œ, ์‹ค์ œ๋กœ ์ฝ”๋“œ์— ์–ด๋–ค ์‹์œผ๋กœ ์ ์šฉ๋˜๋Š”์ง€๋Š” ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ์ž‘์„ฑํ•  ์˜ˆ์ •์ด๋‹ค ๐Ÿซ 

Comments