๐Ÿš€ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ: @SpringBootTest ํ™œ์šฉํ•˜๊ธฐ ๐Ÿงช

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ: @SpringBootTest ํ™œ์šฉํ•˜๊ธฐ ๐Ÿงช

 

 

์•ˆ๋…•, ์นœ๊ตฌ๋“ค! ์˜ค๋Š˜์€ ์ •๋ง ํฅ๋ฏธ์ง„์ง„ํ•œ ์ฃผ์ œ๋กœ ์ฐพ์•„์™”์–ด. ๋ฐ”๋กœ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ์™€ @SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜์— ๋Œ€ํ•ด ๊นŠ์ด ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ๐Ÿ˜Ž ์ด ๊ธ€์„ ์ฝ๊ณ  ๋‚˜๋ฉด ๋„ˆํฌ๋„ ํ…Œ์ŠคํŠธ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ์ž, ์ค€๋น„๋๋‹ˆ? ๊ทธ๋Ÿผ ์‹œ์ž‘ํ•ด๋ณด์ž๊ณ !

์ž ๊น! ํ˜น์‹œ ๋„ˆํฌ ์ค‘์— ์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์ด๋ผ๋Š” ์‚ฌ์ดํŠธ ๋“ค์–ด๋ดค์–ด? ์—ฌ๊ธฐ์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์‹ค๋ ฅ์„ ๊ณต์œ ํ•˜๊ณ  ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋Œ€. ๋‚˜์ค‘์— ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ ๊ธฐ์ˆ ๋กœ ๋ฉ‹์ง„ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ณต์œ ํ•ด๋ณด๋Š” ๊ฑด ์–ด๋•Œ? ๐Ÿค”

1. ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ๋ž€ ๋ญ์•ผ? ๐Ÿคทโ€โ™‚๏ธ

์ž, ๋จผ์ € ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ญ”์ง€๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž. ๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ, ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์•ผ. ๋งˆ์น˜ ์š”๋ฆฌ์‚ฌ๊ฐ€ ์Œ์‹ ๋ง›์„ ๋ณด๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ์šฐ๋ฆฌ๋„ ์ฝ”๋“œ์˜ '๋ง›'์„ ๋ณด๋Š” ๊ฑฐ์ง€! ๐Ÿ˜‹

์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ๋Š” ์šฐ๋ฆฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ’ˆ์งˆ์„ ๋ณด์žฅํ•˜๊ณ , ๋ฒ„๊ทธ๋ฅผ ๋ฏธ๋ฆฌ ์žก์•„๋‚ด๋Š” ๋ฐ ํฐ ๋„์›€์„ ์ค˜.

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

์œ„ ๊ทธ๋ฆผ์„ ๋ณด๋ฉด ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ์˜ ๊ธฐ๋ณธ ํ๋ฆ„์„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด. ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•œ ๋‹ค์Œ, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด์ง€. ์ด ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ์šฐ๋ฆฌ๋Š” ์ ์  ๋” ์•ˆ์ •์ ์ด๊ณ  ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ์–ด.

2. @SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋น„๋ฐ€ ๐Ÿ•ต๏ธโ€โ™€๏ธ

์ž, ์ด์ œ ์šฐ๋ฆฌ์˜ ์ฃผ์ธ๊ณต @SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ์ฐจ๋ก€์•ผ. ์ด ๋…€์„, ๊ฒ‰๋ณด๊ธฐ์—” ๋ณ„ ๊ฑฐ ์•„๋‹Œ ๊ฒƒ ๊ฐ™์ง€๋งŒ, ์‹ค์€ ์—„์ฒญ๋‚œ ํž˜์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด!

@SpringBootTest๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ์†์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•ด์ฃผ๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์–ด๋…ธํ…Œ์ด์…˜์ด์•ผ.

์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ฑฐ์˜ ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์ง€. coolํ•˜์ง€ ์•Š์•„? ๐Ÿ˜Ž

๐Ÿšจ ์ฃผ์˜! @SpringBootTest๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ๊ทธ๋งŒํผ ๋ฆฌ์†Œ์Šค๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด. ๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ๋ฌด์ž‘์ • ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•˜์ž!

์ž, ์ด์ œ @SpringBootTest๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด์ž.


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

@SpringBootTest
public class MyAwesomeTest {

    @Test
    void contextLoads() {
        // ์—ฌ๊ธฐ์„œ ํ…Œ์ŠคํŠธ ๋กœ์ง์„ ์ž‘์„ฑํ•ด
    }
}

์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, ํด๋ž˜์Šค ์œ„์— @SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์˜€์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ค˜. ๊ทธ๋ฆฌ๊ณ  @Test ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ๊ฐ€ ์‹ค์ œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋˜๋Š” ๊ฑฐ์ง€.

ํ•˜์ง€๋งŒ ์ด๊ฒŒ ๋‹ค๊ฐ€ ์•„๋‹ˆ์•ผ! @SpringBootTest๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์˜ต์…˜์„ ์ œ๊ณตํ•ด์„œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๋” ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด:

  • webEnvironment: ์›น ํ™˜๊ฒฝ ์„ค์ •
  • properties: ํ…Œ์ŠคํŠธ์šฉ ํ”„๋กœํผํ‹ฐ ์„ค์ •
  • classes: ํŠน์ • ์„ค์ • ํด๋ž˜์Šค๋งŒ ๋กœ๋“œ

์ด๋Ÿฐ ์˜ต์…˜๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ์ •๋ง ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์— ๋งž๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด, ์›น ์„œ๋ฒ„๋ฅผ ์‹ค์ œ๋กœ ๋„์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์žˆ์ง€:


@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyWebTest {
    // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‹ค์ œ ์›น ์„œ๋ฒ„๊ฐ€ ๋žœ๋ค ํฌํŠธ๋กœ ์‹œ์ž‘๋˜๊ณ , ๊ทธ ์ƒํƒœ์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋ผ. ๋งˆ์น˜ ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ๊ณผ ๋น„์Šทํ•œ ์ƒํ™ฉ์—์„œ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑฐ์ง€!

3. @SpringBootTest์˜ ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ ๐Ÿ”

์ž, ์ด์ œ @SpringBootTest๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋” ์ž์„ธํžˆ ๋“ค์—ฌ๋‹ค๋ณผ ์‹œ๊ฐ„์ด์•ผ. ์ด ๋…€์„์ด ์–ด๋–ป๊ฒŒ ๊ทธ๋ ‡๊ฒŒ ๊ฐ•๋ ฅํ•œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฑธ๊นŒ? ๐Ÿค”

@SpringBootTest๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ์˜ ์ž๋™ ๊ตฌ์„ฑ(Auto-configuration) ๊ธฐ๋Šฅ์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•ด.

์ด๊ฒŒ ๋ฌด์Šจ ๋ง์ด๋ƒ๋ฉด, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•  ๋•Œ์ฒ˜๋Ÿผ ํ•„์š”ํ•œ ๋ชจ๋“  ๋นˆ(Bean)๋“ค์„ ์ž๋™์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  ๊ตฌ์„ฑํ•œ๋‹ค๋Š” ๊ฑฐ์•ผ. ์ •๋ง ๋˜‘๋˜‘ํ•˜์ง€?

@SpringBootTest์˜ ๋™์ž‘ ๊ณผ์ •์„ ๊ฐ„๋‹จํžˆ ์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ๋ผ:

  1. ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ์Šค์บ”ํ•˜๊ณ  @SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ฐœ๊ฒฌํ•ด.
  2. ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•ด.
  3. @SpringBootApplication์ด ๋ถ™์€ ๋ฉ”์ธ ํด๋ž˜์Šค๋ฅผ ์ฐพ์•„ ๊ตฌ์„ฑ ์ •๋ณด๋กœ ์‚ฌ์šฉํ•ด.
  4. application.properties (๋˜๋Š” application.yml) ํŒŒ์ผ์˜ ์„ค์ •์„ ๋กœ๋“œํ•ด.
  5. ์ž๋™ ๊ตฌ์„ฑ์„ ํ†ตํ•ด ํ•„์š”ํ•œ ๋นˆ๋“ค์„ ๋“ฑ๋กํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•ด.
  6. ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ์ถ”๊ฐ€์ ์ธ ๊ตฌ์„ฑ์„ ์ ์šฉํ•ด (์˜ˆ: ์›น ํ™˜๊ฒฝ ์„ค์ •).
  7. ๋ชจ๋“  ์ค€๋น„๊ฐ€ ๋๋‚˜๋ฉด ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•ด.
@SpringBootTest ๋™์ž‘ ๊ณผ์ • @SpringBootTest ๋™์ž‘ ๊ณผ์ • ์–ด๋…ธํ…Œ์ด์…˜ ์Šค์บ” ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ ์„ค์ • ๋กœ๋“œ ๋นˆ ๋“ฑ๋ก/์ดˆ๊ธฐํ™” ํ…Œ์ŠคํŠธ ์‹คํ–‰

์ด ๊ณผ์ •์„ ๊ฑฐ์น˜๋ฉด์„œ @SpringBootTest๋Š” ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ–ˆ์„ ๋•Œ์™€ ๊ฑฐ์˜ ๋™์ผํ•œ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฑฐ์•ผ. ๊ทธ๋ž˜์„œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ์— ์™„๋ฒฝํ•œ ์กฐ๊ฑด์ด ๋˜๋Š” ๊ฑฐ์ง€!

ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์—๋Š” ๋‹น์—ฐํžˆ ๋Œ€๊ฐ€๊ฐ€ ๋”ฐ๋ฅด์ง€. ๋ฐ”๋กœ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์•ผ. ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋กœ๋“œํ•˜๋Š” ๊ฒŒ ๊ฝค ๋ฌด๊ฑฐ์šด ์ž‘์—…์ด๊ฑฐ๋“ . ๊ทธ๋ž˜์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์ฒ˜๋Ÿผ ๊ฐ€๋ฒผ์šด ํ…Œ์ŠคํŠธ์—๋Š” @SpringBootTest ๋Œ€์‹  ๋‹ค๋ฅธ ๊ฐ€๋ฒผ์šด ํ…Œ์ŠคํŠธ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„.

๐Ÿ’ก ๊ฟ€ํŒ: ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ค„์ด๊ณ  ์‹ถ๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋“ค ์‚ฌ์ด์— @DirtiesContext ์–ด๋…ธํ…Œ์ด์…˜์„ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•ด๋ด. ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ ์Šคํ”„๋ง์—๊ฒŒ "์ด ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚˜๋ฉด ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•ด"๋ผ๊ณ  ์•Œ๋ ค์ฃผ๋Š” ์—ญํ• ์„ ํ•ด. ์ปจํ…์ŠคํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์—์„œ๋Š” ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์†๋„๋ฅผ ํฌ๊ฒŒ ๋†’์ผ ์ˆ˜ ์žˆ์ง€!

4. @SpringBootTest ์‹ค์ „ ํ™œ์šฉ ์˜ˆ์ œ ๐Ÿ’ช

์ž, ์ด์ œ @SpringBootTest๋ฅผ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด์ž. ์ด๋ก ๋งŒ ์•Œ๋ฉด ๋ญํ•ด, ์‹ค์ „์—์„œ ์จ๋จน์„ ์ค„ ์•Œ์•„์•ผ์ง€! ๐Ÿ˜‰

4.1 ๊ธฐ๋ณธ์ ์ธ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

๋จผ์ € ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ํ˜•ํƒœ์˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ?


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Test
    void testCreateUser() {
        User user = new User("testuser", "test@example.com");
        User createdUser = userService.createUser(user);
        
        assertNotNull(createdUser.getId());
        assertEquals("testuser", createdUser.getUsername());
        assertEquals("test@example.com", createdUser.getEmail());
    }
}

์ด ํ…Œ์ŠคํŠธ์—์„œ๋Š” UserService๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ์–ด. @SpringBootTest ๋•๋ถ„์— ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ UserService๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์ง€.

4.2 ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…Œ์ŠคํŠธ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ์‹ค์ œ HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ํ™•์ธํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ์ด๋Ÿด ๋•Œ @SpringBootTest์™€ TestRestTemplate์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์•„์ฃผ ํŽธ๋ฆฌํ•ด!


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerIntegrationTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testGetUser() {
        String url = "http://localhost:" + port + "/api/users/1";
        ResponseEntity<user> response = restTemplate.getForEntity(url, User.class);
        
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertNotNull(response.getBody());
        assertEquals("testuser", response.getBody().getUsername());
    }
}
</user>

์—ฌ๊ธฐ์„œ๋Š” @SpringBootTest์˜ webEnvironment ์˜ต์…˜์„ RANDOM_PORT๋กœ ์„ค์ •ํ•ด์„œ ์‹ค์ œ ์›น ์„œ๋ฒ„๋ฅผ ๋žœ๋ค ํฌํŠธ๋กœ ์‹œ์ž‘ํ–ˆ์–ด. ๊ทธ๋ฆฌ๊ณ  TestRestTemplate์„ ์‚ฌ์šฉํ•ด HTTP GET ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ํ™•์ธํ•˜๊ณ  ์žˆ์ง€. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ปจํŠธ๋กค๋Ÿฌ๋ถ€ํ„ฐ ์„œ๋น„์Šค, ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๊นŒ์ง€ ์ „์ฒด ํ”Œ๋กœ์šฐ๋ฅผ ํ•œ ๋ฒˆ์— ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์–ด!

4.3 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ํ…Œ์ŠคํŠธ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๋™๋œ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋„ @SpringBootTest๊ฐ€ ํฐ ๋„์›€์ด ๋ผ. ์˜ˆ๋ฅผ ๋“ค์–ด, JPA๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ํ…Œ์ŠคํŠธํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@Transactional
public class UserRepositoryIntegrationTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void testSaveAndFindUser() {
        User user = new User("testuser", "test@example.com");
        userRepository.save(user);
        
        User foundUser = userRepository.findByUsername("testuser");
        assertNotNull(foundUser);
        assertEquals("test@example.com", foundUser.getEmail());
    }
}

์ด ํ…Œ์ŠคํŠธ์—์„œ๋Š” @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€๋กœ ์‚ฌ์šฉํ–ˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์‹คํ–‰๋˜๊ณ , ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚˜๋ฉด ์ž๋™์œผ๋กœ ๋กค๋ฐฑ๋ผ. ๋•๋ถ„์— ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ œ DB์— ๋‚จ์ง€ ์•Š์•„์„œ ํ…Œ์ŠคํŠธ ๊ฒฉ๋ฆฌ์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์ง€.

4.4 ์™ธ๋ถ€ ์„œ๋น„์Šค ๋ชจํ‚น ํ…Œ์ŠคํŠธ

๋•Œ๋กœ๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์˜์กดํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ์ด๋Ÿด ๋•Œ๋Š” @MockBean์„ ์‚ฌ์šฉํ•ด์„œ ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ๋ชจํ‚นํ•  ์ˆ˜ ์žˆ์–ด.


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

@SpringBootTest
public class WeatherServiceIntegrationTest {

    @Autowired
    private WeatherService weatherService;

    @MockBean
    private ExternalWeatherApi externalWeatherApi;

    @Test
    void testGetWeatherForecast() {
        when(externalWeatherApi.getForecast("Seoul"))
            .thenReturn(new Forecast("Sunny", 25));
        
        Weather weather = weatherService.getWeatherForecast("Seoul");
        
        assertEquals("Sunny", weather.getCondition());
        assertEquals(25, weather.getTemperature());
    }
}

์—ฌ๊ธฐ์„œ๋Š” ExternalWeatherApi๋ฅผ @MockBean์œผ๋กœ ์„ ์–ธํ•ด์„œ ์‹ค์ œ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ ๋„ WeatherService๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ๋‚˜ ์™ธ๋ถ€ ์„œ๋น„์Šค์˜ ๋ถˆ์•ˆ์ •์„ฑ์— ์˜ํ–ฅ๋ฐ›์ง€ ์•Š๊ณ  ์•ˆ์ •์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์ง€.

๐Ÿšจ ์ฃผ์˜! @MockBean์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ด. ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ ์Šคํ”„๋ง ์ปจํ…์ŠคํŠธ๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ณผ๋„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์–ด. ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

5. @SpringBootTest์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ๐Ÿš€

์ž, ์ด์ œ @SpringBootTest์˜ ๋” ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณผ ์ฐจ๋ก€์•ผ. ์ด ๋…€์„์€ ์ •๋ง ๋‹ค์žฌ๋‹ค๋Šฅํ•ด์„œ, ์šฐ๋ฆฌ๊ฐ€ ์ƒ์ƒํ•˜๋Š” ๊ฑฐ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ์–ด!

5.1 ํ…Œ์ŠคํŠธ์šฉ ์„ค์ • ์˜ค๋ฒ„๋ผ์ด๋”ฉ

๋•Œ๋กœ๋Š” ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋งŒ ํŠน๋ณ„ํ•œ ์„ค์ •์„ ์ ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์ง€? ์ด๋Ÿด ๋•Œ @SpringBootTest์˜ properties ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ.


@SpringBootTest(properties = {
    "spring.datasource.url=jdbc:h2:mem:testdb",
    "spring.jpa.hibernate.ddl-auto=create-drop"
})
public class CustomConfigTest {
    // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ ์ง€์ •ํ•œ ํ”„๋กœํผํ‹ฐ๋“ค์ด ๊ธฐ์กด ์„ค์ •์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด, ์œ„ ์ฝ”๋“œ์—์„œ๋Š” ํ…Œ์ŠคํŠธ์šฉ ์ธ๋ฉ”๋ชจ๋ฆฌ H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ–ˆ์–ด. ์‹ค์ œ ์šด์˜ DB๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฑฐ์ง€!

5.2 ํ…Œ์ŠคํŠธ ํ”„๋กœํŒŒ์ผ ํ™œ์„ฑํ™”

์Šคํ”„๋ง์˜ ํ”„๋กœํŒŒ์ผ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด ํ…Œ์ŠคํŠธ์šฉ ์„ค์ •์„ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด @ActiveProfiles ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.


@SpringBootTest
@ActiveProfiles("test")
public class TestProfileActivationTest {
    // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด application-test.properties (๋˜๋Š” application-test.yml) ํŒŒ์ผ์˜ ์„ค์ •์ด ์ ์šฉ๋ผ. ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์— ํŠนํ™”๋œ ์„ค์ •์„ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์•„์ฃผ ํŽธ๋ฆฌํ•˜์ง€!

5.3 ํ…Œ์ŠคํŠธ์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ • ํด๋ž˜์Šค ์‚ฌ์šฉ

๋•Œ๋กœ๋Š” ํ…Œ์ŠคํŠธ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ํŠน๋ณ„ํ•œ ๋นˆ์ด๋‚˜ ์„ค์ •์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์–ด. ์ด๋Ÿด ๋•Œ๋Š” @TestConfiguration์„ ์‚ฌ์šฉํ•ด ํ…Œ์ŠคํŠธ์šฉ ์„ค์ • ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.


@SpringBootTest
public class CustomTestConfigurationTest {

    @TestConfiguration
    static class TestConfig {
        @Bean
        public DataSource dataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
        }
    }

    @Autowired
    private DataSource dataSource;

    @Test
    void testCustomDataSource() {
        assertNotNull(dataSource);
        assertTrue(dataSource instanceof EmbeddedDatabase);
    }
}

์ด ์˜ˆ์ œ์—์„œ๋Š” ํ…Œ์ŠคํŠธ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ž„๋ฒ ๋””๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์„ค์ •ํ•˜๊ณ  ์žˆ์–ด. @TestConfiguration ๋•๋ถ„์— ์ด ์„ค์ •์€ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ์—๋งŒ ์ ์šฉ๋˜๊ณ , ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์•„.

5.4 ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ์™€์˜ ์กฐํ•ฉ

@SpringBootTest๋Š” ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋กœ๋“œํ•˜์ง€๋งŒ, ๋•Œ๋กœ๋Š” ํŠน์ • ๋ ˆ์ด์–ด๋งŒ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์–ด. ์ด๋Ÿด ๋•Œ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.


@SpringBootTest
@AutoConfigureMockMvc
public class ControllerSliceTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testGetUser() throws Exception {
        mockMvc.perform(get("/api/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.username").value("testuser"));
    }
}

์ด ์˜ˆ์ œ์—์„œ๋Š” @SpringBootTest์™€ @AutoConfigureMockMvc๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ์ด์–ด๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ์–ด. MockMvc๋ฅผ ์ด์šฉํ•ด HTTP ์š”์ฒญ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ณ , ์‘๋‹ต์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์ง€.

5.5 ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ

๋งŒ์•ฝ ๋น„๋™๊ธฐ ์ž‘์—…์„ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•œ๋‹ค๋ฉด, @SpringBootTest์™€ ํ•จ๊ป˜ @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.


@SpringBootTest
public class AsyncServiceTest {

    @Autowired
    private AsyncService asyncService;

    @Test
    void testAsyncMethod() throws Exception {
        CompletableFuture<string> future = asyncService.asyncMethod();
        
        String result = future.get(5, TimeUnit.SECONDS);
        assertEquals("Async result", result);
    }
}
</string>

์ด ํ…Œ์ŠคํŠธ์—์„œ๋Š” ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์–ด. CompletableFuture๋ฅผ ์‚ฌ์šฉํ•ด ์ตœ๋Œ€ 5์ดˆ ๋™์•ˆ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋„๋ก ์„ค์ •ํ–ˆ์ง€. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋น„๋™๊ธฐ ์ž‘์—…๋„ ์•ˆ์ „ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์–ด!

๐Ÿ’ก ๊ฟ€ํŒ: ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ๋Š” ํƒ€์ž„์•„์›ƒ ์„ค์ •์— ์ฃผ์˜ํ•ด์•ผ ํ•ด. ๋„ˆ๋ฌด ์งง๊ฒŒ ์„ค์ •ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถˆ์•ˆ์ •ํ•ด์งˆ ์ˆ˜ ์žˆ๊ณ , ๋„ˆ๋ฌด ๊ธธ๊ฒŒ ์„ค์ •ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์–ด. ์ ์ ˆํ•œ ํƒ€์ž„์•„์›ƒ ๊ฐ’์„ ์ฐพ๋Š” ๊ฒŒ ์ค‘์š”ํ•ด!

6. @SpringBootTest ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ โš ๏ธ

@SpringBootTest๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ง€๋งŒ, ๊ทธ๋งŒํผ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ด. ์—ฌ๊ธฐ ๋ช‡ ๊ฐ€์ง€ ์ฃผ์˜์‚ฌํ•ญ์„ ์ •๋ฆฌํ•ด๋ดค์–ด.

6.1 ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„

@SpringBootTest๋Š” ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋กœ๋“œํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์ด ๋ฌธ์ œ๋Š” ๋” ์‹ฌ๊ฐํ•ด์งˆ ์ˆ˜ ์žˆ์ง€.

ํ•ด๊ฒฐ์ฑ…: ๊ฐ€๋Šฅํ•œ ํ•œ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ(@WebMvcTest, @DataJpaTest ๋“ฑ)๋ฅผ ํ™œ์šฉํ•˜๊ณ , ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ @SpringBootTest๋ฅผ ์‚ฌ์šฉํ•ด. ๋˜ํ•œ, ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ๊ฐ„์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์•„.

6.2 ํ…Œ์ŠคํŠธ ๊ฒฉ๋ฆฌ