Skip to content

Joo-Veloper/TestCode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿงช ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

1. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ž€?

  • ์ž‘์€ ์ฝ”๋“œ ๋‹จ์œ„๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ
  • ์ฃผ๋กœ ํด๋ž˜์Šค & ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋กœ ์ง„ํ–‰
  • ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ ๋“ฑ ์™ธ๋ถ€ ํ™˜๊ฒฝ์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ค‘์š”
  • ๋น ๋ฅด๊ณ  ์•ˆ์ •์ ์ด๋ผ๋Š” ์žฅ์ ์ด ์žˆ์Œ

2. ํ…Œ์ŠคํŠธ ๋ฐฉ์‹

โœ… ์ˆ˜๋™ ํ…Œ์ŠคํŠธ: ์‚ฌ๋žŒ์ด ์ง์ ‘ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ
โœ… ์ž๋™ ํ…Œ์ŠคํŠธ: ๊ธฐ๊ณ„๊ฐ€ ์ตœ์ข…์ ์œผ๋กœ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ

  • Java ๊ธฐ๋ฐ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ
  • XUnit ๊ณ„์—ด (Kent Beck)
    • SUnit(Smalltalk), JUnit(Java), NUnit(.NET)
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์„ ๋•๋Š” ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ํ’๋ถ€ํ•œ API ์ œ๊ณต & ๋ฉ”์„œ๋“œ ์ฒด์ด๋‹ ์ง€์› (๊ฐ€๋…์„ฑ ํ–ฅ์ƒ)

๐Ÿ“Œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์„ธ๋ถ„ํ™”

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


โœ… 1. ํ•ดํ”ผ ์ผ€์ด์Šค (Happy Case)

์š”๊ตฌ ์‚ฌํ•ญ์„ ์ •์ƒ์ ์œผ๋กœ ๋งŒ์กฑํ•˜๋Š” ๊ฒฝ์šฐ
์ผ๋ฐ˜์ ์ธ ํ๋ฆ„์—์„œ ๊ธฐ๋Œ€ํ•œ ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธ

๐Ÿ“ ์˜ˆ์‹œ: add(Beverage beverage, int quantity)

  • 1์ž” ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ์ •์ƒ์ ์œผ๋กœ ๋ฆฌ์ŠคํŠธ์— ๋“ค์–ด๊ฐ€๋Š”๊ฐ€?
  • ์—ฌ๋Ÿฌ ์ž”(2์ž” ์ด์ƒ) ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ์ •์ƒ์ ์œผ๋กœ ์ถ”๊ฐ€๋˜๋Š”๊ฐ€?
  • ์Œ๋ฃŒ ์‚ญ์ œ(remove)ํ–ˆ์„ ๋•Œ ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐ๋˜๋Š”๊ฐ€?
  • ์ „์ฒด ์‚ญ์ œ(clear)ํ–ˆ์„ ๋•Œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋น„์–ด ์žˆ๋Š”๊ฐ€?

โŒ 2. ์˜ˆ์™ธ ์ผ€์ด์Šค (Error Case)

๋น„์ •์ƒ์ ์ธ ์ž…๋ ฅ์ด ์ฃผ์–ด์กŒ์„ ๋•Œ ์ ์ ˆํ•œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ™•์ธ
์•”๋ฌต์ (์ผ๋ฐ˜์ ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š”) ์˜ˆ์™ธ์™€ ๋ช…์‹œ์  ์˜ˆ์™ธ ๋ชจ๋‘ ๊ณ ๋ ค

๐Ÿ“ ์˜ˆ์‹œ: add(Beverage beverage, int quantity)

  • 0์ž” ์ถ”๊ฐ€ ์š”์ฒญ (quantity = 0) โ†’ IllegalArgumentException ๋ฐœ์ƒํ•ด์•ผ ํ•จ
  • ์Œ์ˆ˜ ๊ฐœ์ˆ˜ (quantity = -1) โ†’ IllegalArgumentException ๋ฐœ์ƒํ•ด์•ผ ํ•จ
  • ๋งค์šฐ ํฐ ๊ฐœ์ˆ˜ (Integer.MAX_VALUE) โ†’ ์„ฑ๋Šฅ์— ๋ฌธ์ œ๊ฐ€ ์—†๋Š”๊ฐ€?
  • NULL ์Œ๋ฃŒ ์ถ”๊ฐ€ โ†’ NullPointerException ๋˜๋Š” ์ ์ ˆํ•œ ์˜ˆ์™ธ ๋ฐœ์ƒํ•ด์•ผ ํ•จ
  • ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์Œ๋ฃŒ ์‚ญ์ œ (remove ํ˜ธ์ถœ ์‹œ) โ†’ ์˜ˆ์™ธ ๋ฐœ์ƒ ์—ฌ๋ถ€ ํ™•์ธ

๐Ÿ”ฅ 3. ๊ฒฝ๊ณ„๊ฐ’ ํ…Œ์ŠคํŠธ (Boundary Test)

๊ฒฝ๊ณ„์—์„œ ๋™์ž‘์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜ํ–‰๋˜๋Š”์ง€ ํ™•์ธ
๊ฐ’์˜ ์ด์ƒ(โ‰ฅ), ์ดํ•˜(โ‰ค), ์ดˆ๊ณผ(>), ๋ฏธ๋งŒ(<) ๋“ฑ์˜ ์กฐ๊ฑด์„ ํ…Œ์ŠคํŠธ

๐Ÿ“ ์˜ˆ์‹œ: add(Beverage beverage, int quantity)

  • ์ตœ์†Œ ์ฃผ๋ฌธ ๊ฐ€๋Šฅ ๊ฐœ์ˆ˜ (quantity = 1) โ†’ ์ •์ƒ ๋™์ž‘ํ•ด์•ผ ํ•จ
  • ์ตœ์†Œ๋ณด๋‹ค 1๊ฐœ ์ ์€ ๊ฒฝ์šฐ (quantity = 0) โ†’ ์˜ˆ์™ธ ๋ฐœ์ƒํ•ด์•ผ ํ•จ
  • ์ตœ๋Œ€ ์ฃผ๋ฌธ ๊ฐ€๋Šฅ ๊ฐœ์ˆ˜ (quantity = MAX_LIMIT) โ†’ ์ •์ƒ ๋™์ž‘ํ•ด์•ผ ํ•จ
  • ์ตœ๋Œ€๋ณด๋‹ค 1๊ฐœ ์ดˆ๊ณผํ•œ ๊ฒฝ์šฐ (quantity = MAX_LIMIT + 1) โ†’ ์˜ˆ์™ธ ๋ฐœ์ƒํ•ด์•ผ ํ•จ

๐Ÿ“† 4. ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„ ๊ด€๋ จ ํ…Œ์ŠคํŠธ

์‹œ๊ฐ„์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ, ํŠน์ • ์‹œ์  ๋ฐ ๋ฒ”์œ„๋ฅผ ํ…Œ์ŠคํŠธ

๐Ÿ“ ์˜ˆ์‹œ: order() ๋ฉ”์„œ๋“œ (์˜์—…์‹œ๊ฐ„ ๋‚ด ์ฃผ๋ฌธ ๊ฐ€๋Šฅ ์—ฌ๋ถ€)

  • ์˜์—… ์‹œ์ž‘ ์ง์ „ (์˜ค์ „ 9:59) โ†’ ์ฃผ๋ฌธ ์‹คํŒจํ•ด์•ผ ํ•จ
  • ์˜์—… ์‹œ์ž‘ ์‹œ๊ฐ„ (์˜ค์ „ 10:00) โ†’ ์ฃผ๋ฌธ ์„ฑ๊ณตํ•ด์•ผ ํ•จ
  • ์˜์—… ์ข…๋ฃŒ ์ง์ „ (์˜คํ›„ 9:59) โ†’ ์ฃผ๋ฌธ ์„ฑ๊ณตํ•ด์•ผ ํ•จ
  • ์˜์—… ์ข…๋ฃŒ ์‹œ๊ฐ„ (์˜คํ›„ 10:00) โ†’ ์ฃผ๋ฌธ ์‹คํŒจํ•ด์•ผ ํ•จ

๐Ÿ›  5. ๋ฒ”์œ„ ํ…Œ์ŠคํŠธ (Range Test)

ํŠน์ • ๊ฐ’์ด ๋ฒ”์œ„ ๋‚ด์— ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
๋ฒ”์œ„(์ตœ์†Œ ~ ์ตœ๋Œ€), ํŠน์ • ๊ตฌ๊ฐ„, ๊ฐ’ ํฌํ•จ ์—ฌ๋ถ€ ๋“ฑ์„ ํ…Œ์ŠคํŠธ

๐Ÿ“ ์˜ˆ์‹œ: ํ• ์ธ์œจ ๊ฒ€์ฆ (getDiscountRate())

  • ํ• ์ธ์œจ์ด 0% ์ด์ƒ 50% ์ดํ•˜์ผ ๋•Œ ์ •์ƒ ๋™์ž‘ํ•˜๋Š”๊ฐ€?
  • ํ• ์ธ์œจ์ด 50%๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?
  • ํ• ์ธ์œจ์ด ์Œ์ˆ˜์ผ ๋•Œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

๐ŸŽฏ 6. ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์„ค๊ณ„ ์‹œ ๊ณ ๋ คํ•  ์ 

  • ์š”๊ตฌ์‚ฌํ•ญ์„ ๋งŒ์กฑํ•˜๋Š” ์ •์ƒ ๋™์ž‘์„ ํ…Œ์ŠคํŠธํ–ˆ๋Š”๊ฐ€? (ํ•ดํ”ผ ์ผ€์ด์Šค)
  • ์ž…๋ ฅ๊ฐ’์ด ์ž˜๋ชป๋œ ๊ฒฝ์šฐ๋ฅผ ๋ชจ๋‘ ๊ณ ๋ คํ–ˆ๋Š”๊ฐ€? (์˜ˆ์™ธ ์ผ€์ด์Šค)
  • ์ตœ์†Œ, ์ตœ๋Œ€๊ฐ’ ๋“ฑ ๊ฒฝ๊ณ„์—์„œ์˜ ๋™์ž‘์„ ํ™•์ธํ–ˆ๋Š”๊ฐ€? (๊ฒฝ๊ณ„๊ฐ’ ํ…Œ์ŠคํŠธ)
  • ๋ฒ”์œ„, ๊ตฌ๊ฐ„, ๋‚ ์งœ์™€ ๊ด€๋ จ๋œ ์กฐ๊ฑด์„ ์ฒดํฌํ–ˆ๋Š”๊ฐ€? (๋ฒ”์œ„ ํ…Œ์ŠคํŠธ)

๐Ÿงช ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ์˜์—ญ๊ณผ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

์†Œํ”„ํŠธ์›จ์–ด ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ๋•Œ, ์ฝ”๋“œ ๋‚ด ํŠน์ • ์š”์†Œ๋“ค์ด ํ…Œ์ŠคํŠธ๋ฅผ ์–ด๋ ต๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์š”์†Œ๋ฅผ ์‹๋ณ„ํ•˜๊ณ  ๋ถ„๋ฆฌํ•˜๋ฉด, ํ…Œ์ŠคํŠธ์˜ ์‹ ๋ขฐ์„ฑ์„ ๋†’์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿšง 1. ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ์˜์—ญ

ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ค์šด ์ฝ”๋“œ๋Š” ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์™ธ๋ถ€ ํ™˜๊ฒฝ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ์— ์˜์กดํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

๐Ÿ” 1) ๊ด€์ธกํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฐ’์— ์˜์กดํ•˜๋Š” ์ฝ”๋“œ

  • โณ ํ˜„์žฌ ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„ (LocalDateTime.now(), System.currentTimeMillis() ๋“ฑ)
  • ๐ŸŽฒ ๋žœ๋ค ๊ฐ’ (Math.random(), UUID.randomUUID() ๋“ฑ)
  • ๐ŸŒ ์ „์—ญ ๋ณ€์ˆ˜ (์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜)
  • ๐ŸŽค ์‚ฌ์šฉ์ž ์ž…๋ ฅ (Scanner, ์›น ์š”์ฒญ ๋“ฑ)

๐ŸŒ 2) ์™ธ๋ถ€ ์„ธ๊ณ„์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ์ฝ”๋“œ

  • ๐Ÿ–จ๏ธ ํ‘œ์ค€ ์ถœ๋ ฅ (System.out.println())
  • ๐Ÿ“ฉ ๋ฉ”์‹œ์ง€ ๋ฐœ์†ก (์ด๋ฉ”์ผ, SMS ๋“ฑ)
  • ๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ก (INSERT, UPDATE, DELETE ๋“ฑ)
  • ๐Ÿ“ก ๋„คํŠธ์›Œํฌ ์š”์ฒญ (HTTP API ํ˜ธ์ถœ ๋“ฑ)

โœ… 2. ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ

ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋Š” ๊ฐ™์€ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•ด ํ•ญ์ƒ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์™ธ๋ถ€ ํ™˜๊ฒฝ๊ณผ ๋‹จ์ ˆ๋œ ์ˆœ์ˆ˜ํ•œ ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

๐ŸŽฏ 1) ์ˆœ์ˆ˜ ํ•จ์ˆ˜

  • ๊ฐ™์€ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•ด ํ•ญ์ƒ ๊ฐ™์€ ์ถœ๋ ฅ๊ฐ’์„ ๋ฐ˜ํ™˜
  • ์™ธ๋ถ€ ์ƒํƒœ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Œ
  • ์˜ˆ์ œ:
    public int add(int a, int b) {
        return a + b;
    }

๐Ÿ”„ 2) ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ๋ถ„๋ฆฌ

  • ํ˜„์žฌ ์‹œ๊ฐ„์„ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋ฐ›๋„๋ก ์„ค๊ณ„
  • ๋žœ๋ค ๊ฐ’ ๋Œ€์‹  ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์•„ ๊ฒฐ์ •๋ก ์  ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌํ˜„
  • ์˜ˆ์ œ:
    public class OrderService {
        private final Clock clock;
        
        public OrderService(Clock clock) {
            this.clock = clock;
        }
        
        public LocalDateTime getCurrentTime() {
            return LocalDateTime.now(clock);
        }
    }
    Clock fixedClock = Clock.fixed(Instant.parse("2023-01-01T00:00:00Z"), ZoneId.of("UTC"));
    OrderService orderService = new OrderService(fixedClock);
    LocalDateTime time = orderService.getCurrentTime(); // ํ•ญ์ƒ ๋™์ผํ•œ ์‹œ๊ฐ„ ๋ฐ˜ํ™˜

๐Ÿ—๏ธ 3) ์ธํ„ฐํŽ˜์ด์Šค ๋ฐ ์˜์กด์„ฑ ์ฃผ์ž…(DI)

  • ์˜์กด์„ฑ์„ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ƒํ™”ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์‹œ Mock ๊ฐ์ฒด ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ์˜ˆ์ œ:
    public interface TimeProvider {
        LocalDateTime now();
    }
    
    public class RealTimeProvider implements TimeProvider {
        public LocalDateTime now() {
            return LocalDateTime.now();
        }
    }
    
    public class OrderService {
        private final TimeProvider timeProvider;
        
        public OrderService(TimeProvider timeProvider) {
            this.timeProvider = timeProvider;
        }
        
        public LocalDateTime getCurrentTime() {
            return timeProvider.now();
        }
    }
    TimeProvider mockTimeProvider = () -> LocalDateTime.of(2023, 1, 1, 0, 0);
    OrderService orderService = new OrderService(mockTimeProvider);
    LocalDateTime time = orderService.getCurrentTime(); // ํ•ญ์ƒ 2023-01-01 00:00 ๋ฐ˜ํ™˜

๐ŸŽฏ 3. ๊ฒฐ๋ก 

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

  1. ๐Ÿ”น ์ˆœ์ˆ˜ ํ•จ์ˆ˜ ์ž‘์„ฑ โ€“ ๊ฐ™์€ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•ด ๊ฐ™์€ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜.
  2. ๐Ÿ› ๏ธ ์˜์กด์„ฑ ์ฃผ์ž…(DI) ํ™œ์šฉ โ€“ ์™ธ๋ถ€ ํ™˜๊ฒฝ๊ณผ์˜ ์ง์ ‘์ ์ธ ์˜์กด์„ฑ์„ ์ œ๊ฑฐ.
  3. ๐ŸŽญ Mock ๊ฐ์ฒด ํ™œ์šฉ โ€“ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ํ…Œ์ŠคํŠธ ์‹œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜.
  4. โฐ ์‹œ๊ฐ„, ๋žœ๋ค ๊ฐ’ ๋“ฑ์˜ ์š”์†Œ๋ฅผ ์™ธ๋ถ€์—์„œ ์ฃผ์ž… โ€“ ๊ฒฐ์ •๋ก ์  ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค๊ณ„.

๐Ÿงช ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐ TDD

1. TDD (Test-Driven Development)๋ž€?

TDD(Test-Driven Development, ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ)๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•œ ํ›„ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐœ๋ฐœ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ฆ‰, ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค๊ธฐ ์ „์— ๋จผ์ € ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ๊ฒ€์ฆํ•  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

TDD ์‚ฌ์ดํด (Red-Green-Refactor)

  1. RED: ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. (๊ธฐ๋Šฅ์ด ์•„์ง ๊ตฌํ˜„๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ)
  2. GREEN: ๊ธฐ๋Šฅ์„ ์ตœ์†Œํ•œ์œผ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚จ๋‹ค.
  3. BLUE(REFACTOR): ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ (๋ฆฌํŒฉํ† ๋ง)ํ•œ๋‹ค.

ํ•ต์‹ฌ ๊ฐ€์น˜: ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ

  • ๊ตฌํ˜„ ์ฝ”๋“œ์™€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ๋Œ€ํ•ด ์ž์ฃผ, ๋น ๋ฅด๊ฒŒ ํ”ผ๋“œ๋ฐฑ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
  • ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•˜๊ณ , ์œ ์—ฐํ•œ ์ฝ”๋“œ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ์—ฃ์ง€(Edge) ์ผ€์ด์Šค๋ฅผ ๋†“์น˜์ง€ ์•Š๋„๋ก ๋ณด์™„ ๊ฐ€๋Šฅ

2. TDD์˜ ์žฅ์ ๊ณผ ๋ฌธ์ œ์ 

โœ… TDD์˜ ์žฅ์ 

  • ์ฝ”๋“œ ํ’ˆ์งˆ ํ–ฅ์ƒ: ํ…Œ์ŠคํŠธ๊ฐ€ ๋ณด์žฅ๋˜๋ฏ€๋กœ ์•ˆ์ •์„ฑ์ด ๋†’์•„์ง
  • ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ: ๊ธฐ๋Šฅ ๋ณ€๊ฒฝ ์‹œ ๋ฐ”๋กœ ๋ฌธ์ œ์ ์„ ํŒŒ์•… ๊ฐ€๋Šฅ
  • ๋ฆฌํŒฉํ† ๋ง ์šฉ์ด: ๊ธฐ์กด ๊ธฐ๋Šฅ์„ ๋ณดํ˜ธํ•˜๋ฉด์„œ ์ฝ”๋“œ ๊ฐœ์„  ๊ฐ€๋Šฅ
  • ์œ ์—ฐํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์šด ์ฝ”๋“œ: ๋ณต์žก๋„๊ฐ€ ๋‚ฎ๊ณ  ๋ช…ํ™•ํ•œ ๊ตฌ์กฐ ์œ ์ง€ ๊ฐ€๋Šฅ
  • ์—ฃ์ง€ ์ผ€์ด์Šค(Edge Case) ๊ฒ€์ฆ ๊ฐ€๋Šฅ: ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐ•ํ™”

โŒ ์„  ๊ธฐ๋Šฅ ๊ตฌํ˜„, ํ›„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์˜ ๋ฌธ์ œ์ 

  • ํ…Œ์ŠคํŠธ ๋ˆ„๋ฝ ๊ฐ€๋Šฅ์„ฑ: ๊ธฐ๋Šฅ ๊ตฌํ˜„ ํ›„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ค‘์š”ํ•œ ๋ถ€๋ถ„์„ ๋†“์น  ์ˆ˜ ์žˆ์Œ
  • ํŠน์ • ์ผ€์ด์Šค๋งŒ ๊ฒ€์ฆ: ํ•ดํ”ผ ์ผ€์ด์Šค๋งŒ ๊ฒ€์ฆํ•˜๊ณ  ์˜ˆ์™ธ ์ƒํ™ฉ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ
  • ์ž˜๋ชป๋œ ๊ตฌํ˜„์„ ๋Šฆ๊ฒŒ ๋ฐœ๊ฒฌ: ๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ๋’ค๋Šฆ๊ฒŒ ํŒŒ์•… ๊ฐ€๋Šฅ

3. TDD (RED-GREEN-REFACTORING) ์ ์šฉ

๐Ÿ“Œ RED ๋‹จ๊ณ„ (์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ž‘์„ฑ)

@Test
    void calculateTotalPrice() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        Latte latte = new Latte();

        cafeKiosk.add(americano);
        cafeKiosk.add(latte);

        int totalPrice = cafeKiosk.calculateTotalPrice();

        assertThat(totalPrice).isEqualTo(8500);
    }

img.png

  • calculateTotalPrice() ๋ฉ”์„œ๋“œ๊ฐ€ ์•„์ง ๊ตฌํ˜„๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•จ.

๐Ÿ“Œ GREEN ๋‹จ๊ณ„ (์ตœ์†Œํ•œ์˜ ๊ตฌํ˜„ ์ฝ”๋“œ ์ž‘์„ฑ)

 public int calculateTotalPrice() {
       
    return 8500;
}

img_2.png

  • ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋„๋ก ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•จ.
  • ํ•˜์ง€๋งŒ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ํ™•์žฅ์„ฑ์ด ๋ถ€์กฑํ•จ.

๐Ÿ“Œ REFACTORING ๋‹จ๊ณ„ (๋ฆฌํŒฉํ† ๋ง ๋ฐ ๊ฐœ์„ )

public int calculateTotalPrice() {
    int totalPrice = 0;

    for (Beverage beverage : beverages) {
        totalPrice += beverage.getPrice();
    }

    return totalPrice;
}

img_2.png

  • Beverage ๊ฐ์ฒด์˜ getPrice() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋™์ ์œผ๋กœ ์ด ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•˜๋„๋ก ๊ฐœ์„ .

๐Ÿ“Œ REFACTORING ๋‹จ๊ณ„ (์ตœ์ ํ™” - Stream API ํ™œ์šฉ)

 public int calculateTotalPrice() {

       return beverages.stream()
               .mapToInt(Beverage::getPrice)
               .sum();
}
  • Stream API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋” ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์€ ์ฝ”๋“œ๋กœ ๋ฆฌํŒฉํ† ๋งํ•จ.

4. ํšจ๊ณผ์ ์ธ TDD ์‹ค์ฒœ ๋ฐฉ๋ฒ•

  1. ์ž‘์€ ๋‹จ์œ„๋ถ€ํ„ฐ ํ…Œ์ŠคํŠธํ•˜๋ผ
  • ์ฒ˜์Œ๋ถ€ํ„ฐ ํฐ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜์ง€ ๋ง๊ณ  ์ž‘์€ ๋‹จ์œ„๋ถ€ํ„ฐ ์ ์ง„์ ์œผ๋กœ ํ™•์žฅํ•˜๋ผ.
  • ์˜ˆ: add() โ†’ remove() โ†’ calculateTotalPrice() ์ˆœ์œผ๋กœ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  1. ๋ชจ๋“  ์ฃผ์š” ์ผ€์ด์Šค๋ฅผ ๊ณ ๋ คํ•˜๋ผ
  • ํ•ดํ”ผ ์ผ€์ด์Šค (์ •์ƒ์ ์ธ ์ž…๋ ฅ)
  • ์˜ˆ์™ธ ์ผ€์ด์Šค (๋น„์ •์ƒ์ ์ธ ์ž…๋ ฅ)
  • ๊ฒฝ๊ณ„๊ฐ’ ํ…Œ์ŠคํŠธ (์ตœ์†Œ/์ตœ๋Œ€๊ฐ’, ์ดˆ๊ณผ/๋ฏธ๋งŒ ๋“ฑ)
  1. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋กœ ์ž‘์„ฑํ•˜๋ผ
  • ๊ฐ€๋…์„ฑ์ด ๋†’์€ ํ…Œ์ŠคํŠธ ๋„ค์ด๋ฐ (should_๋™์ž‘_์„ค๋ช…)์„ ์‚ฌ์šฉ
  • ์ค‘๋ณต์„ ์ค„์ด๊ณ  ๊ณตํ†ต๋œ ์„ค์ •(setup)์€ @BeforeEach ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ์ •๋ฆฌ
  1. ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ๋ถ„๋ฆฌํ•˜๋ผ
  • ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ์š”์†Œ(์‹œ๊ฐ„, ๋žœ๋ค ๊ฐ’, DB, ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋“ฑ)๋Š” Mocking์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ„๋ฆฌํ•˜๋ผ.

๐Ÿ’ป DisplayName

โœ… ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ž‘์„ฑ ๊ฐ€์ด๋“œ

๐Ÿ“Œ ๋ฌธ์žฅํ˜• ๊ธฐ์ˆ  ์›์น™

  • ๋ช…์‚ฌ์˜ ๋‚˜์—ด์ด ์•„๋‹Œ ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ ํ‘œํ˜„
    โŒ ์Œ๋ฃŒ 1๊ฐœ ์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ โ†’ โœ… ์Œ๋ฃŒ๋ฅผ 1๊ฐœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ ๊ฒฐ๊ณผ๊นŒ์ง€ ๊ธฐ์ˆ 

  • ํ…Œ์ŠคํŠธ ํ–‰์œ„์˜ ๊ฒฐ๊ณผ๊นŒ์ง€ ํฌํ•จ
    โŒ ์Œ๋ฃŒ๋ฅผ 1๊ฐœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
    โœ… ์Œ๋ฃŒ๋ฅผ 1๊ฐœ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฃผ๋ฌธ ๋ชฉ๋ก์— ๋‹ด๊ธด๋‹ค.

๐Ÿ“Œ ๋…ผ๋ฆฌ์ ์ธ ์กฐ๊ฑด ํ‘œํ˜„

  • ๋‹จ์ˆœํ•œ ์กฐ๊ฑด์ด ์•„๋‹Œ, ์ •ํ™•ํ•œ ๋…ผ๋ฆฌ๋ฅผ ๋ฐ˜์˜
    โŒ A์ด๋ฉด B์ด๋‹ค.
    โœ… A์ด๋ฉด B๊ฐ€ ์•„๋‹ˆ๊ณ  C๋‹ค.

๐Ÿ“Œ ๋„๋ฉ”์ธ ์šฉ์–ด ํ™œ์šฉ

  • ๋ฉ”์„œ๋“œ ์ค‘์‹ฌ์ด ์•„๋‹Œ ๋„๋ฉ”์ธ ์ •์ฑ… ๊ด€์ ์—์„œ ์„œ์ˆ 
    โŒ ํŠน์ • ์‹œ๊ฐ„ ์ด์ „์— ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ•˜๋ฉด ์‹คํŒจํ•œ๋‹ค.
    โœ… ์˜์—… ์‹œ์ž‘ ์‹œ๊ฐ„ ์ด์ „์— ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†๋‹ค.

๐ŸŒŸ BDD (Behavior-Driven Development, ํ–‰์œ„ ์ฃผ๋„ ๊ฐœ๋ฐœ)

BDD๋Š” ๐Ÿ› TDD(Test-Driven Development, ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ)์—์„œ ํŒŒ์ƒ๋œ ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•์œผ๋กœ,
๋‹จ์ˆœํžˆ ํ•จ์ˆ˜ ๋‹จ์œ„์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๐Ÿ“– ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐ๋ฐ˜์˜ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ž์ฒด์— ์ง‘์ค‘ํ•˜๋Š” ๋ฐฉ์‹.


๐Ÿ— BDD์˜ ํ…Œ์ŠคํŠธ ๊ตฌ์กฐ: GIVEN-WHEN-THEN

โœ… GIVEN (์ค€๋น„ ๐Ÿ)

  • ์‹œ๋‚˜๋ฆฌ์˜ค ์ง„ํ–‰์„ ์œ„ํ•œ ๋ชจ๋“  ์ค€๋น„ ๊ณผ์ •์„ ์„ค์ •.
  • ์˜ˆ) "๐Ÿ‘ค ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๊ณ , ๐Ÿ›’ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ƒํ’ˆ์ด ๋‹ด๊ฒจ ์žˆ์Œ."

โœ… WHEN (์‹คํ–‰ ๐Ÿš€)

  • ํŠน์ • ํ–‰๋™(์•ก์…˜)์„ ์ˆ˜ํ–‰.
  • ์˜ˆ) "๐Ÿ–ฑ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฐ์ œ ๋ฒ„ํŠผ์„ ํด๋ฆญ."

โœ… THEN (๊ฒ€์ฆ โœ…)

  • ๊ธฐ๋Œ€ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋ช…์‹œํ•˜๊ณ  ๊ฒ€์ฆ.
  • ์˜ˆ) "๐Ÿ’ฐ ๊ฒฐ์ œ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ."

๐ŸŽฏ BDD์˜ ์žฅ์ 

โœ” ๐Ÿ‘ฅ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฐ ๋น„๊ฐœ๋ฐœ์ž์™€์˜ ์›ํ™œํ•œ ์†Œํ†ต
โœ” ๐Ÿ‘€ ํ…Œ์ŠคํŠธ์˜ ๋ช…ํ™•์„ฑ๊ณผ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ
โœ” ๐Ÿ“Œ ์š”๊ตฌ์‚ฌํ•ญ ๊ธฐ๋ฐ˜์˜ ๊ฐœ๋ฐœ ์ง„ํ–‰ ๊ฐ€๋Šฅ


๐Ÿ“Œ @SpringBootTest vs @DataJpaTest

1๏ธโƒฃ @SpringBootTest ๐Ÿ—๏ธ

@SpringBootTest๋Š” Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ๋กœ๋“œํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ์‚ฌ์šฉ.
์ฆ‰, ๋ชจ๋“  ๋นˆ(bean)์„ ๋กœ๋“œํ•˜์—ฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ.

โœ… ํŠน์ง•

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ๋กœ๋“œ
  • ๋ชจ๋“  ๋นˆ์ด ํ™œ์„ฑํ™”๋˜๋ฏ€๋กœ ๋ฌด๊ฑฐ์šด ํ…Œ์ŠคํŠธ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Œ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์„œ๋น„์Šค, ์ปจํŠธ๋กค๋Ÿฌ ๋“ฑ ์ „์ฒด์ ์ธ ๋™์ž‘ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • webEnvironment ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ์›น ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Œ

โœ… ์‚ฌ์šฉ ์˜ˆ์ œ

@SpringBootTest
class MyApplicationTests {

    @Test
    void contextLoads() {
        // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋กœ๋“œ๋˜๋Š”์ง€ ํ™•์ธ
    }
}

โš™๏ธ webEnvironment ์˜ต์…˜

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testHomePage() {
        String body = this.restTemplate.getForObject("/", String.class);
        assertThat(body).contains("Welcome");
    }
}
  • WebEnvironment.RANDOM_PORT โ†’ ๋žœ๋ค ํฌํŠธ๋กœ ์„œ๋ฒ„ ์‹คํ–‰
  • WebEnvironment.MOCK โ†’ ๋‚ด์žฅ ํ†ฐ์บฃ ์—†์ด Mock ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰

2๏ธโƒฃ @DataJpaTest ๐Ÿ—„๏ธ

@DataJpaTest๋Š” JPA ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋กœ๋“œํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
์ฆ‰, Repository ๊ณ„์ธต ํ…Œ์ŠคํŠธ์— ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

โœ… ํŠน์ง•

  • Entity, Repository๋งŒ ๋กœ๋“œ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ จ ๊ธฐ๋Šฅ๋งŒ ํ…Œ์ŠคํŠธ
  • ๊ธฐ๋ณธ์ ์œผ๋กœ H2 ๊ฐ™์€ ์ž„๋ฒ ๋””๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉ
  • ํŠธ๋žœ์žญ์…˜์„ ์ž๋™ ๋กค๋ฐฑํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ›„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์ง€ ์•Š์Œ

โœ… ์‚ฌ์šฉ ์˜ˆ์ œ

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void testSaveAndFind() {
        User user = new User("spring", "spring@example.com");
        userRepository.save(user);

        User foundUser = userRepository.findByEmail("spring@example.com");
        assertThat(foundUser).isNotNull();
    }
}

โš™๏ธ @AutoConfigureTestDatabase ์˜ต์…˜

๊ธฐ๋ณธ์ ์œผ๋กœ H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์‹ค์ œ DB๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ์„ค์ • ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // ์‹ค์ œ DB ์‚ฌ์šฉ
class RealDatabaseTest {
    @Autowired
    private UserRepository userRepository;

    @Test
    void testWithRealDB() {
        // MySQL, PostgreSQL ๋“ฑ ์‹ค์ œ DB์—์„œ ํ…Œ์ŠคํŠธ
    }
}

๐ŸŽฏ ์ •๋ฆฌ

์–ด๋…ธํ…Œ์ด์…˜ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํŠน์ง•
@SpringBootTest ๐ŸŒ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ชจ๋“  ๋นˆ ๋กœ๋“œ, ๋ฌด๊ฑฐ์šด ํ…Œ์ŠคํŠธ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
@DataJpaTest ๐Ÿ—„๏ธ JPA ๊ด€๋ จ ํด๋ž˜์Šค (Repository) JPA๋งŒ ๋กœ๋“œ, ๋น ๋ฅธ ํ…Œ์ŠคํŠธ, ๊ธฐ๋ณธ H2 DB ์‚ฌ์šฉ

โœ… @SpringBootTest๋Š” ์ „์ฒด์ ์ธ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉ
โœ… @DataJpaTest๋Š” JPA ๊ด€๋ จ ํ…Œ์ŠคํŠธ์— ์ตœ์ ํ™”๋œ ์–ด๋…ธํ…Œ์ด์…˜


CQRS์™€ JPA ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ @Transactional ํ™œ์šฉ๋ฒ• ๐Ÿ”ฅ

๐Ÿ“Œ ์ฝ๊ธฐ ์ „์šฉ ์„ค์ • (readOnly = true)

  • โœ… CRUD์—์„œ CUD ๋™์ž‘ X โ†’ ์˜ค์ง ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ (Create, Update, Delete ๋ถˆ๊ฐ€)
  • ๐Ÿš€ JPA ์„ฑ๋Šฅ ํ–ฅ์ƒ โ†’ CUD ์Šค๋ƒ…์ƒท ์ €์žฅโŒ, ๋ณ€๊ฒฝ ๊ฐ์ง€โŒ โ†’ ๋ถˆํ•„์š”ํ•œ ์—ฐ์‚ฐ ๊ฐ์†Œ

๐Ÿ“Œ CQRS ํŒจํ„ด

  • Command ์™€ Query ๋ถ„๋ฆฌ โ†’ ์„œ๋กœ ์ฑ…์ž„์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋…๋ฆฝ์ ์œผ๋กœ ์šด์˜

๐Ÿ“Œ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ฐฉ๋ฒ•

  • @Transactional(readOnly = true) โ†’ ํด๋ž˜์Šค ์ „์ฒด ์ ์šฉ
  • CUD ๋ฉ”์„œ๋“œ์—๋Š” @Transactional์„ ๊ฐœ๋ณ„ ์ ์šฉ โ†’ ๋ช…ํ™•ํ•œ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด ์„ฑ๋Šฅ ์ตœ์ ํ™” & ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด! ๐Ÿš€


์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์–ด๋…ธํ…Œ์ด์…˜ ๋น„๊ต: @NotNull, @NotEmpty, @NotBlank ๐Ÿšซ๐Ÿท๏ธ๐Ÿ“

@NotNull, @NotEmpty, @NotBlank๋Š” Java์—์„œ ๊ฐ์ฒด์˜ ํ•„๋“œ ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜๋“ค์ž…๋‹ˆ๋‹ค.

1. @NotNull ๐Ÿšซ

  • ๋ชฉ์ : ํ•„๋“œ๊ฐ€ null์ด ์•„๋‹ˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ์ œ์•ฝ์„ ์„ค์ •.
  • ํŠน์ง•: null์ธ ๊ฐ’๋งŒ ์ฒดํฌํ•˜๋ฉฐ, ๋นˆ ๋ฌธ์ž์—ด("")์€ ์œ ํšจํ•˜๋‹ค๊ณ  ํŒ๋‹จ.
  • ์‚ฌ์šฉ ์˜ˆ: ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•˜๊ณ  ์‹ถ์„ ๋•Œ.
@NotNull(message = "์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private String name;

2. @NotEmpty ๐Ÿท๏ธ

  • ๋ชฉ์ : ํ•„๋“œ๊ฐ€ null์ด๊ฑฐ๋‚˜ ๋นˆ ๊ฐ’("")์ผ ์ˆ˜ ์—†๋‹ค๋Š” ์ œ์•ฝ์„ ์„ค์ •.
  • ํŠน์ง•: null ๋ฐ ๋นˆ ๋ฌธ์ž์—ด("")์„ ๋ชจ๋‘ ๊ฑฐ๋ถ€. ์ปฌ๋ ‰์…˜์˜ ๊ฒฝ์šฐ, ์š”์†Œ๊ฐ€ ํ•˜๋‚˜ ์ด์ƒ.
  • ์‚ฌ์šฉ ์˜ˆ: ํ…์ŠคํŠธ ํ•„๋“œ์— ์ตœ์†Œํ•œ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉ.
@NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private String email;

3. @NotBlank ๐Ÿ“

  • ๋ชฉ์ : ํ•„๋“œ๊ฐ€ null์ด๊ฑฐ๋‚˜ ๊ณต๋ฐฑ๋งŒ ์žˆ๋Š” ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ์ œ์•ฝ์„ ์„ค์ •.
  • ํŠน์ง•: null, ๋นˆ ๋ฌธ์ž์—ด(""), ๊ณต๋ฐฑ๋งŒ ์žˆ๋Š” ๋ฌธ์ž์—ด(" ")์„ ๋ชจ๋‘ ๊ฑฐ๋ถ€. ์ฆ‰, "๊ณต๋ฐฑ์ด ์•„๋‹Œ ์‹ค์ œ ํ…์ŠคํŠธ"๊ฐ€ ์žˆ์–ด์•ผ ํ•จ.
  • ์‚ฌ์šฉ ์˜ˆ: ํ…์ŠคํŠธ ํ•„๋“œ์—์„œ ์˜๋ฏธ ์žˆ๋Š” ๊ฐ’์ด ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉ.
@NotBlank(message = "์ฃผ์†Œ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private String address;

๐Ÿ—๏ธ ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ vs. ํ•ต์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜

1๏ธโƒฃ ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ (Layered Architecture)

๐Ÿ“Œ ์ „ํ†ต์ ์ธ ์•„ํ‚คํ…์ฒ˜๋กœ, ๊ณ„์ธต๋ณ„๋กœ ์—ญํ• ์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ์‹
๐Ÿ“Œ ๋Œ€ํ‘œ์ ์ธ 3๊ณ„์ธต ๊ตฌ์กฐ (Controller - Service - Repository)

๐Ÿ’ก ๊ตฌ์„ฑ ์š”์†Œ

  1. ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต (Presentation Layer) ๐ŸŽจ
  • ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์‘๋‹ต์„ ๋ฐ˜ํ™˜ (์˜ˆ: Controller)
  1. ๋น„์ฆˆ๋‹ˆ์Šค ๊ณ„์ธต (Business Layer) ๐Ÿ’ก
  • ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹น (์˜ˆ: Service)
  1. ํผ์‹œ์Šคํ„ด์Šค ๊ณ„์ธต (Persistence Layer) ๐Ÿ“ฆ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ CRUD ์ž‘์—…์„ ๋‹ด๋‹น (์˜ˆ: Repository)
  1. ๋„๋ฉ”์ธ ๊ณ„์ธต (Domain Layer, ์„ ํƒ์ ) ๐Ÿ“œ
  • ๋„๋ฉ”์ธ ๋ชจ๋ธ๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ์ •์˜

โœ… ์žฅ์ 
โœ”๏ธ ๊ตฌ์กฐ๊ฐ€ ๋‹จ์ˆœํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›€
โœ”๏ธ ์—ญํ• ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋จ

โŒ ๋‹จ์ 
โ›” ๊ณ„์ธต ๊ฐ„ ๊ฐ•ํ•œ ๊ฒฐํ•ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ
โ›” ๋ณ€ํ™”์— ๋Œ€ํ•œ ์œ ์—ฐ์„ฑ์ด ๋ถ€์กฑ


"๐Ÿ“Œ ๋ ˆ์ด์–ด๋“œ vs. ํ•ต์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜, CQRS, ๋ฝ ์ „๋žต! ๐Ÿš€"

2๏ธโƒฃ ํ•ต์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ (Hexagonal Architecture, Ports & Adapters)

๐Ÿ“Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ•ต์‹ฌ ๋กœ์ง์„ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€ํ•˜๋ ค๋Š” ์•„ํ‚คํ…์ฒ˜
๐Ÿ“Œ "ํ•ต์‚ฌ๊ณ ๋‚ "์ด ์•„๋‹ˆ๋ผ "ํ•ต์‚ฌ๊ณ ๋‚  ๊ฒƒ์ฒ˜๋Ÿผ ์ƒ๊ธด" ๊ตฌ์กฐ ๐Ÿคฃ

๐Ÿ’ก ๊ตฌ์„ฑ ์š”์†Œ

  1. ๋„๋ฉ”์ธ (Domain) ๐ŸŽฏ
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง (๋…๋ฆฝ์ , ๋ณ€๊ฒฝ์ด ์ ์Œ)
  1. ์–ด๋Œ‘ํ„ฐ (Adapters) ๐Ÿ”Œ
  • ๋„๋ฉ”์ธ์„ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ์—ฐ๊ฒฐํ•˜๋Š” ์—ญํ•  (์˜ˆ: Controller, Repository, API Client)
  1. ํฌํŠธ (Ports) ๐Ÿ”„
  • ๋„๋ฉ”์ธ๊ณผ ์–ด๋Œ‘ํ„ฐ ๊ฐ„์˜ ์ธํ„ฐํŽ˜์ด์Šค (์˜ˆ: UseCase, Repository Interface)

โœ… ์žฅ์ 
โœ”๏ธ ๋„๋ฉ”์ธ ๋กœ์ง์ด ์™ธ๋ถ€ ์‹œ์Šคํ…œ(DB, API ๋“ฑ)์— ์ข…์†๋˜์ง€ ์•Š์Œ
โœ”๏ธ ํ…Œ์ŠคํŠธ๊ฐ€ ์šฉ์ดํ•˜๊ณ  ํ™•์žฅ์„ฑ์ด ๋›ฐ์–ด๋‚จ

โŒ ๋‹จ์ 
โ›” ์ดˆ๊ธฐ ์„ค๊ณ„๊ฐ€ ๋ณต์žกํ•  ์ˆ˜ ์žˆ์Œ
โ›” ์ ์ ˆํ•œ ์ธํ„ฐํŽ˜์ด์Šค ์„ค๊ณ„๊ฐ€ ํ•„์š”


๐Ÿ”„ ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ vs. ํ•ต์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ ๋น„๊ต

์•„ํ‚คํ…์ฒ˜ ์žฅ์  ๋‹จ์  ์ ์šฉ ์˜ˆ์‹œ
๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ ๐Ÿ›๏ธ ๊ตฌ์กฐ๊ฐ€ ๋‹จ์ˆœ, ์ดํ•ด ์‰ฌ์›€ ๊ณ„์ธต ๊ฐ„ ๊ฒฐํ•ฉ๋„ ๋†’์Œ ์ „ํ†ต์ ์ธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
ํ•ต์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ ๐Ÿ› ๏ธ ๋„๋ฉ”์ธ ๋…๋ฆฝ์„ฑ, ์œ ์—ฐํ•จ ์„ค๊ณ„ ๋ณต์žก ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค, DDD ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ

โšก CQRS (Command Query Responsibility Segregation)

๐Ÿ“Œ "๋ช…๋ น(Command)๊ณผ ์กฐํšŒ(Query)์„ ๋ถ„๋ฆฌํ•˜๋Š” ํŒจํ„ด"

๐Ÿ’ก CQRS์˜ ํ•ต์‹ฌ ๊ฐœ๋…

  1. Command (์“ฐ๊ธฐ) โœ๏ธ
  • ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ(์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ) ์š”์ฒญ
  • ๋ณดํ†ต Event Sourcing๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋จ
  1. Query (์ฝ๊ธฐ) ๐Ÿ‘€
  • ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ์š”์ฒญ
  • ์ฝ๊ธฐ ์ „์šฉ ๋ชจ๋ธ๋กœ ์ตœ์ ํ™” ๊ฐ€๋Šฅ

โœ… ์žฅ์ 
โœ”๏ธ ์ฝ๊ธฐ์™€ ์“ฐ๊ธฐ์˜ ํ™•์žฅ์„ฑ์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์กฐ์ • ๊ฐ€๋Šฅ
โœ”๏ธ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ์šฉ์ด

โŒ ๋‹จ์ 
โ›” ์„ค๊ณ„๊ฐ€ ๋ณต์žกํ•˜๋ฉฐ, ์œ ์ง€๋ณด์ˆ˜ ๋ถ€๋‹ด ์ฆ๊ฐ€
โ›” ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ฌธ์ œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

๐Ÿ“Œ ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ (ex: Kafka)
  • ๊ณ ์„ฑ๋Šฅ ์ฝ๊ธฐ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•œ ์„œ๋น„์Šค (ex: SNS ํƒ€์ž„๋ผ์ธ)

๐Ÿ” ๋‚™๊ด€์  ๋ฝ (Optimistic Lock) vs. ๋น„๊ด€์  ๋ฝ (Pessimistic Lock)

1๏ธโƒฃ ๋‚™๊ด€์  ๋ฝ (Optimistic Lock) ๐Ÿ˜Š

๐Ÿ“Œ ๊ฒฝ์Ÿ์ด ๋งŽ์ง€ ์•Š์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•˜๊ณ , ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ ์ถฉ๋Œ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋ฐฉ์‹

๐Ÿ’ก ์ž‘๋™ ๋ฐฉ์‹

  • ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ ๋ฒ„์ „(version)์„ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ด
  • ์—…๋ฐ์ดํŠธ ์‹œ ํ˜„์žฌ ๋ฒ„์ „๊ณผ DB์˜ ๋ฒ„์ „์ด ๊ฐ™์€์ง€ ํ™•์ธ
  • ๋‹ค๋ฅด๋ฉด ์ถฉ๋Œ ๋ฐœ์ƒ โ†’ ๊ฐฑ์‹  ์‹คํŒจ

โœ… ์žฅ์ 
โœ”๏ธ ๋ฝ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„ ์„ฑ๋Šฅ์ด ๋›ฐ์–ด๋‚จ
โœ”๏ธ ์ฝ๊ธฐ ์ž‘์—…์ด ๋งŽ์€ ํ™˜๊ฒฝ์—์„œ ์œ ๋ฆฌ

โŒ ๋‹จ์ 
โ›” ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜๋ฉด ์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•จ
โ›” ๋™์‹œ ์ˆ˜์ •์ด ๋งŽ์„ ๊ฒฝ์šฐ ์‹คํŒจ ๊ฐ€๋Šฅ์„ฑ ์ฆ๊ฐ€

๐Ÿ“Œ ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • ์ฝ๊ธฐ ๋น„์ค‘์ด ๋†’์€ ์„œ๋น„์Šค (ex: ์‡ผํ•‘๋ชฐ, ๊ฒŒ์‹œํŒ)
  • JPA์—์„œ @Version์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„
@Version
private Long version;

2๏ธโƒฃ ๋น„๊ด€์  ๋ฝ (Pessimistic Lock) ๐Ÿ˜ก

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

๐Ÿ’ก ์ž‘๋™ ๋ฐฉ์‹

  • ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ์ฆ‰์‹œ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์˜ ์ ‘๊ทผ์„ ์ฐจ๋‹จ
  • SELECT ... FOR UPDATE ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฝ์„ ์„ค์ •

โœ… ์žฅ์ 
โœ”๏ธ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ (๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ ‘๊ทผ ๋ถˆ๊ฐ€)
โœ”๏ธ ๋™์‹œ ์ˆ˜์ •์ด ๋งŽ์€ ๊ฒฝ์šฐ ์•ˆ์ „ํ•จ

โŒ ๋‹จ์ 
โ›” ๋ฝ์ด ๊ฑธ๋ ค ์žˆ๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ๋Œ€๊ธฐํ•ด์•ผ ํ•จ โ†’ ์„ฑ๋Šฅ ์ €ํ•˜
โ›” ๋ฐ๋“œ๋ฝ(๊ต์ฐฉ ์ƒํƒœ) ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ

๐Ÿ“Œ ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • ๋™์‹œ์— ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์€ ๊ฒฝ์šฐ (ex: ์€ํ–‰ ๊ณ„์ขŒ, ์ขŒ์„ ์˜ˆ์•ฝ)
  • JPA์—์„œ๋Š” @Lock(LockModeType.PESSIMISTIC_WRITE)์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Product findByIdForUpdate(@Param("id") Long id);

๐ŸŽฏ ๋‚™๊ด€์  ๋ฝ vs. ๋น„๊ด€์  ๋ฝ ๋น„๊ต

๋ฝ ๋ฐฉ์‹ ์žฅ์  ๋‹จ์  ์‚ฌ์šฉ ์˜ˆ์‹œ
๋‚™๊ด€์  ๋ฝ ๐Ÿ˜Š ์„ฑ๋Šฅ ์ข‹์Œ, ๋ฝ ์—†์Œ ์ถฉ๋Œ ๋ฐœ์ƒ ์‹œ ์žฌ์‹œ๋„ ํ•„์š” ์‡ผํ•‘๋ชฐ, ๊ฒŒ์‹œํŒ
๋น„๊ด€์  ๋ฝ ๐Ÿ˜ก ์ถฉ๋Œ ๋ฐฉ์ง€, ๋ฐ์ดํ„ฐ ์•ˆ์ •์„ฑ ์„ฑ๋Šฅ ์ €ํ•˜, ๋ฐ๋“œ๋ฝ ๊ฐ€๋Šฅ ์€ํ–‰ ๊ณ„์ขŒ, ์ขŒ์„ ์˜ˆ์•ฝ

๐ŸŽฏ ์ •๋ฆฌ

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

๐Ÿงช Test Double

๐Ÿชฃ Dummy : ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š” ๊นกํ†ต ๊ฐ์ฒด

๐Ÿ— Fake : ๋‹จ์ˆœํ•œ ํ˜•ํƒœ๋กœ ๋™์ผํ•œ ๊ธฐ๋Šฅ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ, ํ”„๋กœ๋•์…˜ ์‚ฌ์šฉ์—๋Š” ๋ถ€์กฑํ•œ ๊ฐ์ฒด (์˜ˆ: FakeRepository)

๐Ÿ“Œ Stub : ์š”์ฒญ๋œ ๊ฒƒ์— ๋Œ€ํ•ด ๋ฏธ๋ฆฌ ์ค€๋น„ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฐ์ฒด, ๊ทธ ์™ธ์—๋Š” ์‘๋‹ตํ•˜์ง€ ์•Š์Œ

  • โœ… ์ƒํƒœ ๊ฒ€์ฆ ์ค‘์‹ฌ

๐Ÿ” Spy : Stub์ด๋ฉด์„œ ํ˜ธ์ถœ๋œ ๋‚ด์šฉ์„ ๊ธฐ๋กํ•˜์—ฌ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด

  • ์ผ๋ถ€๋Š” ์‹ค์ œ ๊ฐ์ฒด์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ณ , ์ผ๋ถ€๋งŒ Stubbing ๊ฐ€๋Šฅ

๐ŸŽญ Mock : ํ–‰์œ„์— ๋Œ€ํ•œ ๊ธฐ๋Œ€๋ฅผ ๋ช…์„ธํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“ค์–ด์ง„ ๊ฐ์ฒด

  • ๐ŸŽฏ ํ–‰์œ„ ๊ฒ€์ฆ ์ค‘์‹ฌ

๐Ÿ” BDDMockito๋ž€? ๐Ÿ› ๏ธ

BDDMockito๋Š” Behavior-Driven Development(ํ–‰์œ„ ์ฃผ๋„ ๊ฐœ๋ฐœ) ์Šคํƒ€์ผ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์›ํ•˜๋Š” Mockito API.
๊ธฐ์กด Mockito.when() ๋Œ€์‹  BDDMockito.given()์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ์ข‹์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ๊ฐ€๋Šฅ ๐Ÿ“–โœจ

๐Ÿ“Œ ์ฃผ์š” ํŠน์ง•

  1. BDD ์Šคํƒ€์ผ ์ง€์› ๐Ÿ—๏ธ
  • ๊ธฐ์กด์˜ when๋ณด๋‹ค given์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ์˜ ์˜๋„๋ฅผ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Œ!
  • ์˜ˆ:
    BDDMockito.given(service.callMethod()).willReturn("result");
  1. Mockito์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ ์ œ๊ณต โšก
  • when().thenReturn() โ†’ given().willReturn()
  • when().thenThrow() โ†’ given().willThrow()
  • ๊ธฐ๋Šฅ์ ์œผ๋กœ๋Š” ๊ฐ™์ง€๋งŒ BDD ๋ฐฉ์‹์— ๋งž๋Š” ํ‘œํ˜„์„ ์‚ฌ์šฉ!
  1. ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ ๐Ÿ‘€
  • BDD๋Š” Given-When-Then ํŒจํ„ด์„ ๋”ฐ๋ฅด๋ฏ€๋กœ, given()์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ๋” ์ฝ๊ธฐ ์‰ฌ์›Œ์ง€๊ณ  ๋…ผ๋ฆฌ์ ์ธ ํ๋ฆ„์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
  • ex:
    // Given
    given(userService.getUser("123")).willReturn(new User("Joo"));
    
    // When
    User result = userService.getUser("123");
    
    // Then
    assertEquals("Joo", result.getName());
  1. ํ…Œ์ŠคํŠธ์˜ ๋ชฉ์ ์„ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๐ŸŽฏ
  • when()์€ ์ผ๋ฐ˜์ ์ธ ๋ชจํ‚น ํ‘œํ˜„์ด๋ผ ํ…Œ์ŠคํŠธ์˜ ๋ชฉ์ ์ด ๋ชจํ˜ธํ•  ์ˆ˜ ์žˆ์Œ
  • given()์„ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์ „ ์กฐ๊ฑด(Given)์ด ๊ฐ•์กฐ๋˜์–ด ํ…Œ์ŠคํŠธ์˜ ์˜๋„ ํŒŒ์•…์ด ์‰ฌ์›€

๐Ÿท๏ธ @ParameterizedTest๋ž€?

JUnit 5์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋‹ค๋ฅธ ์ž…๋ ฅ๊ฐ’์„ ์‚ฌ์šฉํ•ด ๊ฐ™์€ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜๋ณต ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜. ๐Ÿ“Œ


๐ŸŽฏ ์™œ ์‚ฌ์šฉํ• ๊นŒ?

โœ… ๋ฐ˜๋ณต๋˜๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ค„์ด๊ธฐ ๐Ÿ“
โœ… ๋‹ค์–‘ํ•œ ์ž…๋ ฅ๊ฐ’์„ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ ๐ŸŽฒ
โœ… ํ…Œ์ŠคํŠธ ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด ๐Ÿ› ๏ธ


๐ŸŽฌ ์‚ฌ์šฉ ์˜ˆ์ œ

@ParameterizedTest  // โœ… ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž…๋ ฅ๊ฐ’์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Œ
@ValueSource(ints = {1, 2, 3, 4, 5})  // ๐ŸŽฒ ์ž…๋ ฅ๊ฐ’์„ ๋ฐฐ์—ด๋กœ ์ „๋‹ฌ
void testIsEven(int number) {  
    assertTrue(number % 2 == 0, "์ง์ˆ˜์ธ์ง€ ํ™•์ธ โŒ");
}

๐Ÿ” ์œ„ ์ฝ”๋“œ๋Š” 1, 2, 3, 4, 5๋ฅผ ๊ฐ๊ฐ number๋กœ ๋ฐ›์•„ ์ง์ˆ˜์ธ์ง€ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ


๐ŸŽญ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ์ œ๊ณต ๋ฐฉ์‹

1๏ธโƒฃ ๋ฐฐ์—ด ๊ฐ’ ์ „๋‹ฌ โ†’ @ValueSource ๐ŸŽฒ
2๏ธโƒฃ CSV ๊ฐ’ ์ „๋‹ฌ โ†’ @CsvSource ๐Ÿ“‘
3๏ธโƒฃ CSV ํŒŒ์ผ์—์„œ ์ฝ๊ธฐ โ†’ @CsvFileSource ๐Ÿ“‚
4๏ธโƒฃ ๋ฉ”์„œ๋“œ์—์„œ ๊ฐ’ ์ œ๊ณต โ†’ @MethodSource ๐Ÿ› ๏ธ
5๏ธโƒฃ ๊ฐ์ฒด ๋ณ€ํ™˜ ๊ฐ€๋Šฅ โ†’ @ArgumentsSource ๐ŸŽญ


โœ… ๊ฒฐ๋ก 

๐Ÿ’ก @ParameterizedTest๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ˜๋ณต์ ์ธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋กœ ์‰ฝ๊ฒŒ ๊ฒ€์ฆ ๊ฐ€๋Šฅ! ๐Ÿš€


๐Ÿ“Œ @DynamicTest (JUnit 5)
JUnit 5์—์„œ **๋™์  ํ…Œ์ŠคํŠธ(Dynamic Test)**๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์• ๋„ˆํ…Œ์ด์…˜
์ผ๋ฐ˜์ ์ธ @Test๋Š” ์ •์ ์œผ๋กœ ์ •์˜๋˜์ง€๋งŒ, @DynamicTest๋Š” ์‹คํ–‰ ์‹œ์ ์— ๋™์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ƒ์„ฑ ๊ฐ€๋Šฅ ๐Ÿš€


๐Ÿ›  ํŠน์ง•

โœ… ๋™์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ƒ์„ฑ ๊ฐ€๋Šฅ
โœ… @Test ๋Œ€์‹  ์‚ฌ์šฉ (์ผ๋ฐ˜์ ์œผ๋กœ @TestFactory์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋จ)
โœ… ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
โœ… Stream, Collection ๋“ฑ์„ ํ™œ์šฉ ๊ฐ€๋Šฅ


๐Ÿ“ ์‚ฌ์šฉ ์˜ˆ์‹œ

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.util.List;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertTrue;

class DynamicTestExample {

    @TestFactory
    Stream<DynamicTest> dynamicTests() {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        return numbers.stream()
                .map(n -> DynamicTest.dynamicTest(
                        "Checking if " + n + " is positive",
                        () -> assertTrue(n > 0)
                ));
    }
}

๐Ÿ”ฅ ์„ค๋ช…

1๏ธโƒฃ @TestFactory๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑ โœจ
2๏ธโƒฃ List.of(1, 2, 3, 4, 5) ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ DynamicTest๋ฅผ ์ƒ์„ฑ ๐Ÿ”„
3๏ธโƒฃ DynamicTest.dynamicTest("ํ…Œ์ŠคํŠธ ์ด๋ฆ„", ์‹คํ–‰ํ•  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ) ์‚ฌ์šฉ ๐Ÿ› 
4๏ธโƒฃ assertTrue(n > 0)๋กœ ๊ฐ ์ˆซ์ž๊ฐ€ ์–‘์ˆ˜์ธ์ง€ ๊ฒ€์‚ฌ โœ…


๐ŸŽฏ ์–ธ์ œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์€๊ฐ€?

๐Ÿ“ ์—ฌ๋Ÿฌ ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•ด ๋ฐ˜๋ณต ํ…Œ์ŠคํŠธํ•  ๋•Œ
๐Ÿ“ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•ด์•ผ ํ•  ๋•Œ
๐Ÿ“ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฆฌ์ŠคํŠธ, ์ปฌ๋ ‰์…˜, ์ŠคํŠธ๋ฆผ ํ˜•ํƒœ๋กœ ์กด์žฌํ•  ๋•Œ


๐Ÿ“ ํ…Œ์ŠคํŠธ ์›์น™ & ๊ฐœ๋… ์ •๋ฆฌ

  • ํ…Œ์ŠคํŠธ ํ•˜๋‚˜ ๋‹น ๋ชฉ์ ์€ ํ•˜๋‚˜!
    ย ย ย ย โœ… ๋‹จ์ผ ์ฑ…์ž„ ์›์น™ (SRP) ์ ์šฉ
    ย ย ย ย โœ… ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ๋งŒ ๊ฒ€์ฆ

  • ์™„๋ฒฝ ์ œ์–ด ๐Ÿ•น๏ธ
    ย ย ย ย โœ… ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํ๋ฆ„๊ณผ ๋ฐ์ดํ„ฐ ์ƒํƒœ๋ฅผ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ ๊ด€๋ฆฌ

  • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์˜ ๋…๋ฆฝ์„ฑ ๐ŸŒ
    ย ย ย ย โœ… ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „ํ›„ ์ƒํƒœ ์ดˆ๊ธฐํ™”
    ย ย ย ย โœ… ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก ๊ตฌ์„ฑ

  • Test Fixture ๐Ÿ—๏ธ
    ย ย ย ย โœ… ํ…Œ์ŠคํŠธ ์‹คํ–‰์„ ์œ„ํ•œ ๊ณ ์ •๋œ ์ดˆ๊ธฐ ์ƒํƒœ ์„ค์ •

  • ๐Ÿ—‘๏ธ deleteAll() vs. deleteAllInBatch()
    ย ย ย ย โœ… deleteAll() โ†’ ์—”ํ‹ฐํ‹ฐ ๊ฐœ๋ณ„ ์‚ญ์ œ (์„ฑ๋Šฅโ†“)
    ย ย ย ย โœ… deleteAllInBatch() โ†’ ํ•œ ๋ฒˆ์— ์‚ญ์ œ (์„ฑ๋Šฅโ†‘)

  • ๐Ÿ“Œ @ParameterizedTest & @DynamicTest
    ย ย ย ย โœ… @ParameterizedTest โ†’ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ๊ฐ’์œผ๋กœ ๋ฐ˜๋ณต ํ…Œ์ŠคํŠธ
    ย ย ย ย โœ… @DynamicTest โ†’ ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ์ƒ์„ฑ

  • โš™๏ธ ์ˆ˜ํ–‰ ํ™˜๊ฒฝ ํ†ตํ•ฉ
    ย ย ย ย โœ… ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ๊ณผ ์‹ค์ œ ํ™˜๊ฒฝ์„ ์ตœ๋Œ€ํ•œ ์œ ์‚ฌํ•˜๊ฒŒ ๊ตฌ์„ฑ

  • ๐Ÿ”’ Private Method Test
    ย ย ย ย โœ… ๊ฐ„์ ‘์ ์œผ๋กœ ๊ณต๊ฐœ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ
    ย ย ย ย โœ… Reflection ํ™œ์šฉ ๊ฐ€๋Šฅ (ํ•˜์ง€๋งŒ ์‹ ์ค‘ํ•˜๊ฒŒ!)


๐Ÿ“œ RestDoc vs Swagger

๐Ÿ“ RestDoc

โœ… ์žฅ์ 

  • โœ… ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•ด์•ผ ๋ฌธ์„œ ์ƒ์„ฑ ๐Ÿ†
  • โœ… ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— ๋น„์นจํˆฌ์  ๐Ÿš€

โŒ ๋‹จ์ 

  • โŒ ์ฝ”๋“œ๋Ÿ‰ ๋งŽ์Œ ๐Ÿ“„๐Ÿ“„
  • โŒ ์„ค์ • ์–ด๋ ค์›€ ๐Ÿ”ง๐Ÿ˜ต

๐Ÿ“– Swagger

โœ… ์žฅ์ 

  • โœ… ์ ์šฉ ์‰ฌ์›€ ๐ŸŽฏ
  • โœ… ๋ฌธ์„œ์—์„œ ๋ฐ”๋กœ API ํ˜ธ์ถœ ๊ฐ€๋Šฅ โšก๐Ÿ”—

โŒ ๋‹จ์ 

  • โŒ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— ์นจํˆฌ์  ๐Ÿ—๏ธ
  • โŒ ํ…Œ์ŠคํŠธ์™€ ๋ฌด๊ด€ํ•˜์—ฌ ์‹ ๋ขฐ๋„ ๋‚ฎ์„ ์ˆ˜ ์žˆ์Œ ๐Ÿค”

๐Ÿ—๏ธ ์•„ํ‚คํ…ํŠธ ๋ชฉํ‘œ

  • ๐Ÿ’ก ์ธ์  ์ž์› ์ ˆ๊ฐ
  • ๐Ÿ“‘ ์ •์ฑ…์„ ๋งŒ๋“ค๊ณ  ์„ธ๋ถ€์‚ฌํ•ญ์„ ๋ฏธ๋ฃจ๋Š” ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ

๊ฐœ๋ฐœ ์ดˆ๊ธฐ ๋‹จ๊ณ„์—์„œ ์„ ํƒ์„ ๋ฏธ๋ฃจ๊ธฐ:

  • ์ดˆ๊ธฐ์—๋Š” Oracle์ธ์ง€ MySQL์ธ์ง€ ์„ ํƒํ•  ํ•„์š” ์—†์Œ ๐Ÿ›‘
  • ์ดˆ๊ธฐ์—๋Š” Nginx์ธ์ง€ Apache์ธ์ง€ ์„ ํƒํ•  ํ•„์š” ์—†์Œ ๐Ÿ›‘
  • ์ดˆ๊ธฐ์—๋Š” REST์ธ์ง€ GraphQL์ธ์ง€ ์„ ํƒํ•  ํ•„์š” ์—†์Œ ๐Ÿ›‘
  • ์ดˆ๊ธฐ์—๋Š” Spring์„ ์ ์šฉํ•  ํ•„์š” ์—†์Œ ๐Ÿ›‘

๐Ÿ“ ๋„๋ฉ”์ธ ์šฐ์„  ๊ฐœ๋ฐœ:

  • ๐Ÿ’ก ๋„๋ฉ”์ธ์ด ๋จผ์ € ๊ฐœ๋ฐœ๋˜์–ด์•ผ ํ•จ

๐Ÿ”„ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ Spring Hexagonal Architecture

  • ๐Ÿ”„ ์˜์กด์„ฑ ์—ญ์ „ (Port-Adapter Pattern)

    • โš™๏ธ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ: ์‹œ์Šคํ…œ์˜ ๊ฐ ๋ถ€๋ถ„์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ถ„๋ฆฌ
    • ๐Ÿ”’ ๊ณ ๋ฆฝํ™”: ์„œ๋กœ์˜ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋„๋ก ๊ฐ ๋ชจ๋“ˆ์„ ๋…๋ฆฝ์ ์œผ๋กœ ๋งŒ๋“ฆ
    • ๐Ÿ”„ ์˜์กด์„ฑ ์—ญ์ „: ์™ธ๋ถ€ ์‹œ์Šคํ…œ์˜ ๋ณ€ํ™”๊ฐ€ ๋‚ด๋ถ€ ์‹œ์Šคํ…œ์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ์ตœ์†Œํ™”
  • ๐ŸŒฑ ์ง„ํ™”ํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜

    • ๋” ์ด์ƒ ํ•„์š”ํ•˜์ง€ ์•Š์€ โ›” ๊ณ„์ธต์€ ์ œ๊ฑฐํ•˜๊ณ , ์‹œ์Šคํ…œ์˜ ๊ตฌ์กฐ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ์œ ์ง€

๐Ÿ“Š ๋ณ€ํ™” ์˜ˆ์‹œ


โœ… ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ vs ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜

๐Ÿ”ท ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜์˜ ํŠน์ง• ๋ฐ ๋ฌธ์ œ์ 

  • ๊ตฌ์กฐ: ํ”„๋ ˆ์  ํ…Œ์ด์…˜ โ†’ ์„œ๋น„์Šค โ†’ ๋„๋ฉ”์ธ โ†’ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ(DB) ์ˆœ์œผ๋กœ ์ˆ˜์ง ๊ณ„์ธต ๊ตฌ์„ฑ

  • ๋ฌธ์ œ์ :

    • ํ•˜์œ„ ๊ณ„์ธต(ํŠนํžˆ DB)์— ์˜์กดํ•˜๊ฒŒ ๋˜๋ฉฐ, DB ์ค‘์‹ฌ์˜ ์‚ฌ๊ณ ๋ฐฉ์‹์„ ๊ฐ–๊ฒŒ ๋จ
    • ์ƒํ–ฅ์‹ ์ ‘๊ทผ ์‹œ: JPA(Entity)๋ถ€ํ„ฐ ์„ค๊ณ„ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒฝํ–ฅ
    • ํ•˜ํ–ฅ์‹ ์ ‘๊ทผ ์‹œ: ํ”„๋ ˆ์ž„์›Œํฌ(Spring, MVC ๋“ฑ)๋ถ€ํ„ฐ ๊ณ ๋ คํ•˜๊ฒŒ ๋จ
  • โ†’ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ค‘์‹ฌ ์„ค๊ณ„๊ฐ€ ์–ด๋ ค์›Œ์ง


๐Ÿ”ท ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜์˜ ํŠน์ง•

  • ๊ตฌ์กฐ: ๋„๋ฉ”์ธ(ํ•ต์‹ฌ ๋กœ์ง)์„ ์ค‘์‹ฌ์œผ๋กœ, ํฌํŠธ(์ธํ„ฐํŽ˜์ด์Šค)์™€ ์–ด๋Œ‘ํ„ฐ(ํ”„๋ ˆ์ž„์›Œํฌ, DB, UI ๋“ฑ)๊ฐ€ ๋ฐ”๊นฅ์— ์œ„์น˜

  • ์žฅ์ :

    • ์ƒํ–ฅ์‹ ์ ‘๊ทผ์ด ์ž์—ฐ์Šค๋Ÿฌ์›€
    • ํ”„๋ ˆ์ž„์›Œํฌ, DB, UI ๋“ฑ ์™ธ๋ถ€ ์š”์†Œ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์–ด ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ
    • ๋„๋ฉ”์ธ ๋กœ์ง์„ ์ค‘์‹ฌ์œผ๋กœ ์‚ฌ๊ณ ํ•  ์ˆ˜ ์žˆ์Œ

โœ… ํ—˜๋ธ” ๊ฐ์ฒด(Humble Object) ํŒจํ„ด

๐Ÿ”ธ ์ •์˜

  • ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ์š”์†Œ(GUI, DB ๋“ฑ)๋ฅผ ๋ถ„๋ฆฌ๋œ ํ—˜๋ธ” ๊ฐ์ฒด๋กœ ๋”ฐ๋กœ ๋ถ„๋ฆฌ
  • ๋ณธ์งˆ์ ์ธ ๋กœ์ง(๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)๊ณผ ํ—˜๋ธ” ๊ฐ์ฒด(ํ…Œ์ŠคํŠธ ์–ด๋ ค์šด ์š”์†Œ)๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„

๐Ÿ”ธ ์™œ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

  • "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฐ”๊พผ๋‹ค๊ณ  ๊ณ„์‚ฐ ๋กœ์ง์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ ๋œ๋‹ค"
  • "ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋ฐ”๊พผ๋‹ค๊ณ  ๊ณ„์‚ฐ ๋กœ์ง์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ ๋œ๋‹ค"
  • "์‹ฌ์ง€์–ด ์–ธ์–ด๋ฅผ ๋ฐ”๊ฟ”๋„ ๊ณ„์‚ฐ ๋กœ์ง์€ ์œ ์ง€๋˜์–ด์•ผ ํ•œ๋‹ค"
  • ์„ค๊ณ„์˜ ํ•ต์‹ฌ์€ ์˜์กด์„ฑ์˜ ๋ฐฉํ–ฅ ์ œ์–ด์™€ ๊ณ ๋ฆฝ์„ฑ ์œ ์ง€
  • ํ—˜๋ธ” ๊ฐ์ฒด๋Š” ์™ธ๋ถ€ ์˜์กด์„ฑ์ด ํฌ๋ฏ€๋กœ, ํ…Œ์ŠคํŠธ์—์„œ ๋ฐฐ์ œํ•˜๊ฑฐ๋‚˜ Mocking ๋Œ€์ƒ์œผ๋กœ ์‚ผ์Œ

โœ… ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜์—์„œ์˜ ์–ด๋Œ‘ํ„ฐ์™€ ์œ ์Šค์ผ€์ด์Šค

๐Ÿ”ท ์–ด๋Œ‘ํ„ฐ(Adapter)์˜ ์—ญํ• 

์–ด๋Œ‘ํ„ฐ๋Š” ์™ธ๋ถ€ ์‹œ์Šคํ…œ(์›น, DB, ๋ฉ”์‹œ์ง€ ๋“ฑ)๊ณผ ๋‚ด๋ถ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(๋„๋ฉ”์ธ/์œ ์Šค์ผ€์ด์Šค) ์‚ฌ์ด์˜ ์ž…ยท์ถœ๋ ฅ ์—ฐ๊ฒฐ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”ธ 1. Input ์–ด๋Œ‘ํ„ฐ (์˜ˆ: ์›น ์–ด๋Œ‘ํ„ฐ, Controller)

๐Ÿ“Œ ์ฃผ์š” ์—ญํ• :

  1. HTTP ์š”์ฒญ์„ ์ž๋ฐ” ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ (ex. JSON โ†’ DTO)
  2. ๊ถŒํ•œ ๊ฒ€์‚ฌ
  3. ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ
  4. ์›น DTO๋ฅผ โ†’ ์œ ์Šค์ผ€์ด์Šค ์ž…๋ ฅ ๋ชจ๋ธ๋กœ ๋ณ€ํ™˜
  5. ์œ ์Šค์ผ€์ด์Šค ํ˜ธ์ถœ
  6. ์œ ์Šค์ผ€์ด์Šค ๊ฒฐ๊ณผ๋ฅผ HTTP ์‘๋‹ต ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
  7. HTTP ์‘๋‹ต ๋ฐ˜ํ™˜

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ ์›น ์–ด๋Œ‘ํ„ฐ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ค‘๋ณต ๊ตฌํ˜„ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, โ€œ์›น ์ž…๋ ฅ DTO โ†’ ์œ ์Šค์ผ€์ด์Šค ์ž…๋ ฅ ๋ชจ๋ธโ€๋กœ ๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•œ์ง€๋งŒ ๊ฒ€์ฆํ•˜๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”ธ 2. Output ์–ด๋Œ‘ํ„ฐ (์˜ˆ: JPA ์–ด๋Œ‘ํ„ฐ, Repository)

๐Ÿ“Œ ์ฃผ์š” ์—ญํ• :

  1. ์œ ์Šค์ผ€์ด์Šค ๋˜๋Š” ๋„๋ฉ”์ธ์œผ๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ์„ ๋ฐ›์Œ
  2. ์ด๋ฅผ DB ํฌ๋งท(JPA Entity)๋กœ ๋งคํ•‘
  3. DB์— ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์กฐํšŒ
  4. ๊ฒฐ๊ณผ๋ฅผ ๋„๋ฉ”์ธ ๋ชจ๋ธ ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌ๋งท์œผ๋กœ ๋ณ€ํ™˜
  5. ์œ ์Šค์ผ€์ด์Šค ๋˜๋Š” ๋„๋ฉ”์ธ์œผ๋กœ ์ถœ๋ ฅ ๋ฐ˜ํ™˜

๐Ÿ’ก ์ž๋ฐ”์—์„œ๋Š” ๋ณดํ†ต JPA๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ž…๋ ฅ ๋ชจ๋ธ์„ JPA ์—”ํ‹ฐํ‹ฐ๋กœ ๋งคํ•‘ํ•˜๊ณ , DB ๊ฒฐ๊ณผ๋ฅผ ๋„๋ฉ”์ธ ๋ชจ๋ธ ๋˜๋Š” DTO๋กœ ๋‹ค์‹œ ๋ณ€ํ™˜ํ•˜๋Š” ๊ตฌ์กฐ๊ฐ€ ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.


โœ… ์œ ์Šค์ผ€์ด์Šค์˜ ์—ญํ• 

  • ๋„๋ฉ”์ธ ๊ทœ์น™์„ ๋ฐ”ํƒ•์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ๋ฆ„์„ ์ •์˜
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์กฐํ•ฉํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๋‹จ์œ„
  • ์™ธ๋ถ€ ์˜์กด์„ฑ(DB, UI ๋“ฑ)์— ๋Œ€ํ•ด ๋ฌด์ง€(็„ก็Ÿฅ)ํ•ด์•ผ ํ•˜๋ฉฐ, ์ˆœ์ˆ˜ํ•œ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์œ ์ง€๋จ

๐Ÿงฉ ๊ด€๊ณ„ ์ •๋ฆฌ ์š”์•ฝ

์š”์†Œ ์—ญํ•  ์š”์•ฝ
Input ์–ด๋Œ‘ํ„ฐ ์™ธ๋ถ€ ์š”์ฒญ(HTTP ๋“ฑ)์„ ์œ ์Šค์ผ€์ด์Šค ์ž…๋ ฅ ๋ชจ๋ธ๋กœ ๋ณ€ํ™˜ ํ›„ ํ˜ธ์ถœ
์œ ์Šค์ผ€์ด์Šค ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰ (๋„๋ฉ”์ธ ์กฐํ•ฉ ๋ฐ ํ๋ฆ„ ์ œ์–ด)
Output ์–ด๋Œ‘ํ„ฐ ์œ ์Šค์ผ€์ด์Šค ์š”์ฒญ์„ DB ํ˜•์‹(JPA ๋“ฑ)์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ์ฒ˜๋ฆฌ

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages