- ์์ ์ฝ๋ ๋จ์๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฒ์ฆํ๋ ํ ์คํธ
- ์ฃผ๋ก ํด๋์ค & ๋ฉ์๋ ๋จ์๋ก ์งํ
- ์ธ๋ถ ๋คํธ์ํฌ ๋ฑ ์ธ๋ถ ํ๊ฒฝ์ ์์กดํ์ง ์๋ ๊ฒ์ด ์ค์
- ๋น ๋ฅด๊ณ ์์ ์ ์ด๋ผ๋ ์ฅ์ ์ด ์์
โ
์๋ ํ
์คํธ: ์ฌ๋์ด ์ง์ ํ์ธํ๋ ํ
์คํธ
โ
์๋ ํ
์คํธ: ๊ธฐ๊ณ๊ฐ ์ต์ข
์ ์ผ๋ก ๊ฒ์ฆํ๋ ํ
์คํธ
- Java ๊ธฐ๋ฐ ๋จ์ ํ ์คํธ ํ๋ ์์ํฌ
- XUnit ๊ณ์ด (Kent Beck)
- SUnit(Smalltalk), JUnit(Java), NUnit(.NET)
- ํ ์คํธ ์ฝ๋ ์์ฑ์ ๋๋ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ํ๋ถํ API ์ ๊ณต & ๋ฉ์๋ ์ฒด์ด๋ ์ง์ (๊ฐ๋ ์ฑ ํฅ์)
ํ
์คํธ๋ฅผ ๋์ฑ ์ฒ ์ ํ๊ฒ ์งํํ๋ ค๋ฉด, ๋ค์ํ ์ผ์ด์ค๋ฅผ ๋ถ๋ฅํ๊ณ ๊ฒฝ๊ณ๊ฐ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
์๋์ ๊ฐ์ด ํดํผ ์ผ์ด์ค, ์์ธ ์ผ์ด์ค, ๊ฒฝ๊ณ๊ฐ ํ
์คํธ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์ ๋ฆฌํ ์ ์์ต๋๋ค.
์๊ตฌ ์ฌํญ์ ์ ์์ ์ผ๋ก ๋ง์กฑํ๋ ๊ฒฝ์ฐ
์ผ๋ฐ์ ์ธ ํ๋ฆ์์ ๊ธฐ๋ํ ๋๋ก ๋์ํ๋์ง ํ์ธ
- 1์ ์ถ๊ฐํ์ ๋ ์ ์์ ์ผ๋ก ๋ฆฌ์คํธ์ ๋ค์ด๊ฐ๋๊ฐ?
- ์ฌ๋ฌ ์(2์ ์ด์) ์ถ๊ฐํ์ ๋ ์ ์์ ์ผ๋ก ์ถ๊ฐ๋๋๊ฐ?
- ์๋ฃ ์ญ์ (remove)ํ์ ๋ ๋ฆฌ์คํธ์์ ์ ๊ฑฐ๋๋๊ฐ?
- ์ ์ฒด ์ญ์ (clear)ํ์ ๋ ๋ฆฌ์คํธ๊ฐ ๋น์ด ์๋๊ฐ?
๋น์ ์์ ์ธ ์ ๋ ฅ์ด ์ฃผ์ด์ก์ ๋ ์ ์ ํ ์์ธ๊ฐ ๋ฐ์ํ๋์ง ํ์ธ
์๋ฌต์ (์ผ๋ฐ์ ์ผ๋ก ์์๋๋) ์์ธ์ ๋ช ์์ ์์ธ ๋ชจ๋ ๊ณ ๋ ค
- 0์ ์ถ๊ฐ ์์ฒญ (
quantity = 0) โIllegalArgumentException๋ฐ์ํด์ผ ํจ - ์์ ๊ฐ์ (
quantity = -1) โIllegalArgumentException๋ฐ์ํด์ผ ํจ - ๋งค์ฐ ํฐ ๊ฐ์ (
Integer.MAX_VALUE) โ ์ฑ๋ฅ์ ๋ฌธ์ ๊ฐ ์๋๊ฐ? - NULL ์๋ฃ ์ถ๊ฐ โ
NullPointerException๋๋ ์ ์ ํ ์์ธ ๋ฐ์ํด์ผ ํจ - ์กด์ฌํ์ง ์๋ ์๋ฃ ์ญ์ (
removeํธ์ถ ์) โ ์์ธ ๋ฐ์ ์ฌ๋ถ ํ์ธ
๊ฒฝ๊ณ์์ ๋์์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ํ๋๋์ง ํ์ธ
๊ฐ์ ์ด์(โฅ), ์ดํ(โค), ์ด๊ณผ(>), ๋ฏธ๋ง(<) ๋ฑ์ ์กฐ๊ฑด์ ํ ์คํธ
- ์ต์ ์ฃผ๋ฌธ ๊ฐ๋ฅ ๊ฐ์ (
quantity = 1) โ ์ ์ ๋์ํด์ผ ํจ - ์ต์๋ณด๋ค 1๊ฐ ์ ์ ๊ฒฝ์ฐ (
quantity = 0) โ ์์ธ ๋ฐ์ํด์ผ ํจ - ์ต๋ ์ฃผ๋ฌธ ๊ฐ๋ฅ ๊ฐ์ (
quantity = MAX_LIMIT) โ ์ ์ ๋์ํด์ผ ํจ - ์ต๋๋ณด๋ค 1๊ฐ ์ด๊ณผํ ๊ฒฝ์ฐ (
quantity = MAX_LIMIT + 1) โ ์์ธ ๋ฐ์ํด์ผ ํจ
์๊ฐ์ด ์ค์ํ ๊ฒฝ์ฐ, ํน์ ์์ ๋ฐ ๋ฒ์๋ฅผ ํ ์คํธ
- ์์
์์ ์ง์ (
์ค์ 9:59) โ ์ฃผ๋ฌธ ์คํจํด์ผ ํจ - ์์
์์ ์๊ฐ (
์ค์ 10:00) โ ์ฃผ๋ฌธ ์ฑ๊ณตํด์ผ ํจ - ์์
์ข
๋ฃ ์ง์ (
์คํ 9:59) โ ์ฃผ๋ฌธ ์ฑ๊ณตํด์ผ ํจ - ์์
์ข
๋ฃ ์๊ฐ (
์คํ 10:00) โ ์ฃผ๋ฌธ ์คํจํด์ผ ํจ
ํน์ ๊ฐ์ด ๋ฒ์ ๋ด์ ์กด์ฌํ๋์ง ํ์ธ
๋ฒ์(์ต์ ~ ์ต๋), ํน์ ๊ตฌ๊ฐ, ๊ฐ ํฌํจ ์ฌ๋ถ ๋ฑ์ ํ ์คํธ
- ํ ์ธ์จ์ด 0% ์ด์ 50% ์ดํ์ผ ๋ ์ ์ ๋์ํ๋๊ฐ?
- ํ ์ธ์จ์ด 50%๋ฅผ ์ด๊ณผํ๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋๊ฐ?
- ํ ์ธ์จ์ด ์์์ผ ๋ ์์ธ๊ฐ ๋ฐ์ํ๋๊ฐ?
- ์๊ตฌ์ฌํญ์ ๋ง์กฑํ๋ ์ ์ ๋์์ ํ ์คํธํ๋๊ฐ? (ํดํผ ์ผ์ด์ค)
- ์ ๋ ฅ๊ฐ์ด ์๋ชป๋ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ๊ณ ๋ คํ๋๊ฐ? (์์ธ ์ผ์ด์ค)
- ์ต์, ์ต๋๊ฐ ๋ฑ ๊ฒฝ๊ณ์์์ ๋์์ ํ์ธํ๋๊ฐ? (๊ฒฝ๊ณ๊ฐ ํ ์คํธ)
- ๋ฒ์, ๊ตฌ๊ฐ, ๋ ์ง์ ๊ด๋ จ๋ ์กฐ๊ฑด์ ์ฒดํฌํ๋๊ฐ? (๋ฒ์ ํ ์คํธ)
์ํํธ์จ์ด ํ ์คํธ๋ฅผ ์ํํ ๋, ์ฝ๋ ๋ด ํน์ ์์๋ค์ด ํ ์คํธ๋ฅผ ์ด๋ ต๊ฒ ๋ง๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ด๋ฌํ ์์๋ฅผ ์๋ณํ๊ณ ๋ถ๋ฆฌํ๋ฉด, ํ ์คํธ์ ์ ๋ขฐ์ฑ์ ๋์ด๊ณ ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ํ ์ ์์ต๋๋ค.
ํ ์คํธ๊ฐ ์ด๋ ค์ด ์ฝ๋๋ ์คํํ ๋๋ง๋ค ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๊ฑฐ๋ ์ธ๋ถ ํ๊ฒฝ๊ณผ์ ์ํธ์์ฉ์ ์์กดํ๋ ์ฝ๋์ ๋๋ค.
- โณ ํ์ฌ ๋ ์ง ๋ฐ ์๊ฐ (
LocalDateTime.now(),System.currentTimeMillis()๋ฑ) - ๐ฒ ๋๋ค ๊ฐ (
Math.random(),UUID.randomUUID()๋ฑ) - ๐ ์ ์ญ ๋ณ์ (์ํ๊ฐ ๋ณ๊ฒฝ๋ ์ ์๋ ๋ณ์)
- ๐ค ์ฌ์ฉ์ ์
๋ ฅ (
Scanner, ์น ์์ฒญ ๋ฑ)
- ๐จ๏ธ ํ์ค ์ถ๋ ฅ (
System.out.println()) - ๐ฉ ๋ฉ์์ง ๋ฐ์ก (์ด๋ฉ์ผ, SMS ๋ฑ)
- ๐๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ก (INSERT, UPDATE, DELETE ๋ฑ)
- ๐ก ๋คํธ์ํฌ ์์ฒญ (HTTP API ํธ์ถ ๋ฑ)
ํ ์คํธํ๊ธฐ ์ฌ์ด ์ฝ๋๋ ๊ฐ์ ์ ๋ ฅ๊ฐ์ ๋ํด ํญ์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ฉฐ, ์ธ๋ถ ํ๊ฒฝ๊ณผ ๋จ์ ๋ ์์ํ ํํ๋ฅผ ๊ฐ์ง๋๋ค.
- ๊ฐ์ ์ ๋ ฅ๊ฐ์ ๋ํด ํญ์ ๊ฐ์ ์ถ๋ ฅ๊ฐ์ ๋ฐํ
- ์ธ๋ถ ์ํ์ ์ํฅ์ ์ฃผ์ง ์์
- ์์ :
public int add(int a, int b) { return a + b; }
- ํ์ฌ ์๊ฐ์ ์ง์ ํธ์ถํ๋ ๊ฒ์ด ์๋๋ผ, ์ธ๋ถ์์ ์ฃผ์ ๋ฐ๋๋ก ์ค๊ณ
- ๋๋ค ๊ฐ ๋์ ์์กด์ฑ์ ์ฃผ์ ๋ฐ์ ๊ฒฐ์ ๋ก ์ ํ ์คํธ ๊ฐ๋ฅํ๋๋ก ๊ตฌํ
- ์์ :
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(); // ํญ์ ๋์ผํ ์๊ฐ ๋ฐํ
- ์์กด์ฑ์ ์ธํฐํ์ด์ค๋ก ์ถ์ํํ์ฌ ํ ์คํธ ์ 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 ๋ฐํ
ํ ์คํธํ๊ธฐ ์ด๋ ค์ด ์์๋ฅผ ๋ถ๋ฆฌํ์ฌ, ์์ธก ๊ฐ๋ฅํ ํ๊ฒฝ์์ ํ ์คํธ๋ฅผ ์ํํ ์ ์๋๋ก ์ค๊ณํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด๋ฅผ ์ํด ๋ค์๊ณผ ๊ฐ์ ์์น์ ์ ์ฉํ ์ ์์ต๋๋ค.
- ๐น ์์ ํจ์ ์์ฑ โ ๊ฐ์ ์ ๋ ฅ๊ฐ์ ๋ํด ๊ฐ์ ๊ฒฐ๊ณผ ๋ฐํ.
- ๐ ๏ธ ์์กด์ฑ ์ฃผ์ (DI) ํ์ฉ โ ์ธ๋ถ ํ๊ฒฝ๊ณผ์ ์ง์ ์ ์ธ ์์กด์ฑ์ ์ ๊ฑฐ.
- ๐ญ Mock ๊ฐ์ฒด ํ์ฉ โ ์ธ๋ถ ์์คํ ๊ณผ์ ์ํธ์์ฉ์ ํ ์คํธ ์ ์๋ฎฌ๋ ์ด์ .
- โฐ ์๊ฐ, ๋๋ค ๊ฐ ๋ฑ์ ์์๋ฅผ ์ธ๋ถ์์ ์ฃผ์ โ ๊ฒฐ์ ๋ก ์ ํ ์คํธ ๊ฐ๋ฅํ๋๋ก ์ค๊ณ.
TDD(Test-Driven Development, ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ)๋ ํ ์คํธ๋ฅผ ๋จผ์ ์์ฑํ ํ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๊ฐ๋ฐ ๋ฐฉ์์ ๋๋ค. ์ฆ, ๊ธฐ๋ฅ์ ๋ง๋ค๊ธฐ ์ ์ ๋จผ์ ํด๋น ๊ธฐ๋ฅ์ ๊ฒ์ฆํ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ ์ํ๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค.
- RED: ์คํจํ๋ ํ ์คํธ๋ฅผ ์์ฑํ๋ค. (๊ธฐ๋ฅ์ด ์์ง ๊ตฌํ๋์ง ์์๊ธฐ ๋๋ฌธ)
- GREEN: ๊ธฐ๋ฅ์ ์ต์ํ์ผ๋ก ๊ตฌํํ์ฌ ํ ์คํธ๋ฅผ ํต๊ณผ์ํจ๋ค.
- BLUE(REFACTOR): ์ค๋ณต์ ์ ๊ฑฐํ๊ณ ์ฝ๋๋ฅผ ๊ฐ์ (๋ฆฌํฉํ ๋ง)ํ๋ค.
- ๊ตฌํ ์ฝ๋์ ํ ์คํธ ์ฝ๋์ ๋ํด ์์ฃผ, ๋น ๋ฅด๊ฒ ํผ๋๋ฐฑ ๋ฐ์ ์ ์์
- ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ๊ณ , ์ ์ฐํ ์ฝ๋ ์์ฑ ๊ฐ๋ฅ
- ์ฃ์ง(Edge) ์ผ์ด์ค๋ฅผ ๋์น์ง ์๋๋ก ๋ณด์ ๊ฐ๋ฅ
- ์ฝ๋ ํ์ง ํฅ์: ํ ์คํธ๊ฐ ๋ณด์ฅ๋๋ฏ๋ก ์์ ์ฑ์ด ๋์์ง
- ๋น ๋ฅธ ํผ๋๋ฐฑ: ๊ธฐ๋ฅ ๋ณ๊ฒฝ ์ ๋ฐ๋ก ๋ฌธ์ ์ ์ ํ์ ๊ฐ๋ฅ
- ๋ฆฌํฉํ ๋ง ์ฉ์ด: ๊ธฐ์กด ๊ธฐ๋ฅ์ ๋ณดํธํ๋ฉด์ ์ฝ๋ ๊ฐ์ ๊ฐ๋ฅ
- ์ ์ฐํ๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์ด ์ฝ๋: ๋ณต์ก๋๊ฐ ๋ฎ๊ณ ๋ช ํํ ๊ตฌ์กฐ ์ ์ง ๊ฐ๋ฅ
- ์ฃ์ง ์ผ์ด์ค(Edge Case) ๊ฒ์ฆ ๊ฐ๋ฅ: ์์์น ๋ชปํ ์์ธ ์ฒ๋ฆฌ ๊ฐํ
- ํ ์คํธ ๋๋ฝ ๊ฐ๋ฅ์ฑ: ๊ธฐ๋ฅ ๊ตฌํ ํ ํ ์คํธ๋ฅผ ์์ฑํ๋ฉด ์ค์ํ ๋ถ๋ถ์ ๋์น ์ ์์
- ํน์ ์ผ์ด์ค๋ง ๊ฒ์ฆ: ํดํผ ์ผ์ด์ค๋ง ๊ฒ์ฆํ๊ณ ์์ธ ์ํฉ์ ๊ณ ๋ คํ์ง ์์ ๊ฐ๋ฅ์ฑ์ด ์์
- ์๋ชป๋ ๊ตฌํ์ ๋ฆ๊ฒ ๋ฐ๊ฒฌ: ๊ธฐ๋ฅ์ด ์ ์์ ์ผ๋ก ๋์ํ๋์ง ๋ค๋ฆ๊ฒ ํ์ ๊ฐ๋ฅ
@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);
}- calculateTotalPrice() ๋ฉ์๋๊ฐ ์์ง ๊ตฌํ๋์ง ์์์ผ๋ฏ๋ก ํ ์คํธ๊ฐ ์คํจํจ.
public int calculateTotalPrice() {
return 8500;
}- ํ ์คํธ๋ฅผ ํต๊ณผํ๋๋ก ๊ฐ์ฅ ๊ฐ๋จํ ์ฝ๋๋ก ๊ธฐ๋ฅ์ ๊ตฌํํจ.
- ํ์ง๋ง ํ๋์ฝ๋ฉ๋ ๊ฐ์ ์ฌ์ฉํ์ฌ ํ์ฅ์ฑ์ด ๋ถ์กฑํจ.
public int calculateTotalPrice() {
int totalPrice = 0;
for (Beverage beverage : beverages) {
totalPrice += beverage.getPrice();
}
return totalPrice;
}- Beverage ๊ฐ์ฒด์ getPrice() ๋ฉ์๋๋ฅผ ํ์ฉํ์ฌ ๋์ ์ผ๋ก ์ด ๊ฐ๊ฒฉ์ ๊ณ์ฐํ๋๋ก ๊ฐ์ .
public int calculateTotalPrice() {
return beverages.stream()
.mapToInt(Beverage::getPrice)
.sum();
}- Stream API๋ฅผ ํ์ฉํ์ฌ ๋ ๊ฐ๊ฒฐํ๊ณ ๊ฐ๋ ์ฑ์ด ์ข์ ์ฝ๋๋ก ๋ฆฌํฉํ ๋งํจ.
- ์์ ๋จ์๋ถํฐ ํ ์คํธํ๋ผ
- ์ฒ์๋ถํฐ ํฐ ๊ธฐ๋ฅ์ ํ ์คํธํ์ง ๋ง๊ณ ์์ ๋จ์๋ถํฐ ์ ์ง์ ์ผ๋ก ํ์ฅํ๋ผ.
- ์: add() โ remove() โ calculateTotalPrice() ์์ผ๋ก ํ ์คํธ ์์ฑ
- ๋ชจ๋ ์ฃผ์ ์ผ์ด์ค๋ฅผ ๊ณ ๋ คํ๋ผ
- ํดํผ ์ผ์ด์ค (์ ์์ ์ธ ์ ๋ ฅ)
- ์์ธ ์ผ์ด์ค (๋น์ ์์ ์ธ ์ ๋ ฅ)
- ๊ฒฝ๊ณ๊ฐ ํ ์คํธ (์ต์/์ต๋๊ฐ, ์ด๊ณผ/๋ฏธ๋ง ๋ฑ)
- ํ ์คํธ ์ฝ๋๋ฅผ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ๊ตฌ์กฐ๋ก ์์ฑํ๋ผ
- ๊ฐ๋ ์ฑ์ด ๋์ ํ ์คํธ ๋ค์ด๋ฐ (should_๋์_์ค๋ช )์ ์ฌ์ฉ
- ์ค๋ณต์ ์ค์ด๊ณ ๊ณตํต๋ ์ค์ (setup)์ @BeforeEach ๋ฑ์ ํ์ฉํ์ฌ ์ ๋ฆฌ
- ์ธ๋ถ ์์กด์ฑ์ ๋ถ๋ฆฌํ๋ผ
- ํ ์คํธํ๊ธฐ ์ด๋ ค์ด ์์(์๊ฐ, ๋๋ค ๊ฐ, DB, ๋คํธ์ํฌ ์์ฒญ ๋ฑ)๋ Mocking์ ์ฌ์ฉํ์ฌ ๋ถ๋ฆฌํ๋ผ.
๐ ๋ฌธ์ฅํ ๊ธฐ์ ์์น
- ๋ช
์ฌ์ ๋์ด์ด ์๋ ์์ ํ ๋ฌธ์ฅ์ผ๋ก ํํ
โ์๋ฃ 1๊ฐ ์ถ๊ฐ ํ ์คํธโ โ์๋ฃ๋ฅผ 1๊ฐ ์ถ๊ฐํ ์ ์๋ค.
๐ ๊ฒฐ๊ณผ๊น์ง ๊ธฐ์
- ํ
์คํธ ํ์์ ๊ฒฐ๊ณผ๊น์ง ํฌํจ
โ์๋ฃ๋ฅผ 1๊ฐ ์ถ๊ฐํ ์ ์๋ค.
โ์๋ฃ๋ฅผ 1๊ฐ ์ถ๊ฐํ๋ฉด ์ฃผ๋ฌธ ๋ชฉ๋ก์ ๋ด๊ธด๋ค.
๐ ๋ ผ๋ฆฌ์ ์ธ ์กฐ๊ฑด ํํ
- ๋จ์ํ ์กฐ๊ฑด์ด ์๋, ์ ํํ ๋
ผ๋ฆฌ๋ฅผ ๋ฐ์
โA์ด๋ฉด B์ด๋ค.
โA์ด๋ฉด B๊ฐ ์๋๊ณ C๋ค.
๐ ๋๋ฉ์ธ ์ฉ์ด ํ์ฉ
- ๋ฉ์๋ ์ค์ฌ์ด ์๋ ๋๋ฉ์ธ ์ ์ฑ
๊ด์ ์์ ์์
โํน์ ์๊ฐ ์ด์ ์ ์ฃผ๋ฌธ์ ์์ฑํ๋ฉด ์คํจํ๋ค.
โ์์ ์์ ์๊ฐ ์ด์ ์ ์ฃผ๋ฌธ์ ์์ฑํ ์ ์๋ค.
BDD๋ ๐ TDD(Test-Driven Development, ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ)์์ ํ์๋ ๊ฐ๋ฐ ๋ฐฉ๋ฒ์ผ๋ก,
๋จ์ํ ํจ์ ๋จ์์ ํ
์คํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ, ๐ ์๋๋ฆฌ์ค ๊ธฐ๋ฐ์ ํ
์คํธ ์ผ์ด์ค ์์ฒด์ ์ง์คํ๋ ๋ฐฉ์.
โ GIVEN (์ค๋น ๐)
- ์๋๋ฆฌ์ค ์งํ์ ์ํ ๋ชจ๋ ์ค๋น ๊ณผ์ ์ ์ค์ .
- ์) "๐ค ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ๋์ด ์๊ณ , ๐ ์ฅ๋ฐ๊ตฌ๋์ ์ํ์ด ๋ด๊ฒจ ์์."
โ WHEN (์คํ ๐)
- ํน์ ํ๋(์ก์ )์ ์ํ.
- ์) "๐ฑ ์ฌ์ฉ์๊ฐ ๊ฒฐ์ ๋ฒํผ์ ํด๋ฆญ."
โ THEN (๊ฒ์ฆ โ )
- ๊ธฐ๋ํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ช ์ํ๊ณ ๊ฒ์ฆ.
- ์) "๐ฐ ๊ฒฐ์ ๊ฐ ์๋ฃ๋์๋ค๋ ๋ฉ์์ง๊ฐ ํ์."
โ ๐ฅ ๋น์ฆ๋์ค ๋ฐ ๋น๊ฐ๋ฐ์์์ ์ํํ ์ํต
โ ๐ ํ
์คํธ์ ๋ช
ํ์ฑ๊ณผ ๊ฐ๋
์ฑ ํฅ์
โ ๐ ์๊ตฌ์ฌํญ ๊ธฐ๋ฐ์ ๊ฐ๋ฐ ์งํ ๊ฐ๋ฅ
@SpringBootTest๋ Spring Boot ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด ์ปจํ
์คํธ๋ฅผ ๋ก๋ํ์ฌ ํ
์คํธ๋ฅผ ์คํํ ๋ ์ฌ์ฉ.
์ฆ, ๋ชจ๋ ๋น(bean)์ ๋ก๋ํ์ฌ ํตํฉ ํ
์คํธ๋ฅผ ์ํํ ์ ์์.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ฒด ์ปจํ ์คํธ๋ฅผ ๋ก๋
- ๋ชจ๋ ๋น์ด ํ์ฑํ๋๋ฏ๋ก ๋ฌด๊ฑฐ์ด ํ ์คํธ๊ฐ ๋ ์ ์์
- ๋ฐ์ดํฐ๋ฒ ์ด์ค, ์๋น์ค, ์ปจํธ๋กค๋ฌ ๋ฑ ์ ์ฒด์ ์ธ ๋์ ํ ์คํธ ๊ฐ๋ฅ
webEnvironment์ต์ ์ ์ฌ์ฉํด ์น ์๋ฒ๋ฅผ ์คํํ ์๋ ์์
@SpringBootTest
class MyApplicationTests {
@Test
void contextLoads() {
// ์ ํ๋ฆฌ์ผ์ด์
์ปจํ
์คํธ๊ฐ ์ ์์ ์ผ๋ก ๋ก๋๋๋์ง ํ์ธ
}
}@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 ํ๊ฒฝ์์ ์คํ
@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();
}
}๊ธฐ๋ณธ์ ์ผ๋ก 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 ๊ด๋ จ ํ
์คํธ์ ์ต์ ํ๋ ์ด๋
ธํ
์ด์
- โ CRUD์์ CUD ๋์ X โ ์ค์ง ์ฝ๊ธฐ๋ง ๊ฐ๋ฅ (Create, Update, Delete ๋ถ๊ฐ)
- ๐ JPA ์ฑ๋ฅ ํฅ์ โ CUD ์ค๋ ์ท ์ ์ฅโ, ๋ณ๊ฒฝ ๊ฐ์งโ โ ๋ถํ์ํ ์ฐ์ฐ ๊ฐ์
- Command ์ Query ๋ถ๋ฆฌ โ ์๋ก ์ฑ ์์ ๋ถ๋ฆฌํ์ฌ ๋ ๋ฆฝ์ ์ผ๋ก ์ด์
- @Transactional(readOnly = true) โ ํด๋์ค ์ ์ฒด ์ ์ฉ
- CUD ๋ฉ์๋์๋ @Transactional์ ๊ฐ๋ณ ์ ์ฉ โ ๋ช ํํ ํธ๋์ญ์ ๊ด๋ฆฌ ๊ฐ๋ฅ
์ด๋ ๊ฒ ์ค์ ํ๋ฉด ์ฑ๋ฅ ์ต์ ํ & ์ ์ง๋ณด์ ์ฉ์ด! ๐
@NotNull, @NotEmpty, @NotBlank๋ Java์์ ๊ฐ์ฒด์ ํ๋ ๊ฐ์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํํ ๋ ์ฌ์ฉํ๋ ์ด๋
ธํ
์ด์
๋ค์
๋๋ค.
- ๋ชฉ์ : ํ๋๊ฐ
null์ด ์๋์ด์ผ ํ๋ค๋ ์ ์ฝ์ ์ค์ . - ํน์ง:
null์ธ ๊ฐ๋ง ์ฒดํฌํ๋ฉฐ, ๋น ๋ฌธ์์ด("")์ ์ ํจํ๋ค๊ณ ํ๋จ. - ์ฌ์ฉ ์: ๊ฐ์ฒด๊ฐ ์กด์ฌํ๋์ง ๋ฐ๋์ ํ์ธํ๊ณ ์ถ์ ๋.
@NotNull(message = "์ด๋ฆ์ ํ์์
๋๋ค.")
private String name;- ๋ชฉ์ : ํ๋๊ฐ
null์ด๊ฑฐ๋ ๋น ๊ฐ("")์ผ ์ ์๋ค๋ ์ ์ฝ์ ์ค์ . - ํน์ง:
null๋ฐ ๋น ๋ฌธ์์ด("")์ ๋ชจ๋ ๊ฑฐ๋ถ. ์ปฌ๋ ์ ์ ๊ฒฝ์ฐ, ์์๊ฐ ํ๋ ์ด์. - ์ฌ์ฉ ์: ํ ์คํธ ํ๋์ ์ต์ํ์ ๋ฐ์ดํฐ๊ฐ ํ์ํ ๋ ์ฌ์ฉ.
@NotEmpty(message = "์ด๋ฉ์ผ์ ํ์์
๋๋ค.")
private String email;- ๋ชฉ์ : ํ๋๊ฐ
null์ด๊ฑฐ๋ ๊ณต๋ฐฑ๋ง ์๋ ๋ฌธ์์ด์ด ์๋์ด์ผ ํ๋ค๋ ์ ์ฝ์ ์ค์ . - ํน์ง:
null, ๋น ๋ฌธ์์ด(""), ๊ณต๋ฐฑ๋ง ์๋ ๋ฌธ์์ด(" ")์ ๋ชจ๋ ๊ฑฐ๋ถ. ์ฆ, "๊ณต๋ฐฑ์ด ์๋ ์ค์ ํ ์คํธ"๊ฐ ์์ด์ผ ํจ. - ์ฌ์ฉ ์: ํ ์คํธ ํ๋์์ ์๋ฏธ ์๋ ๊ฐ์ด ํ์ํ ๋ ์ฌ์ฉ.
@NotBlank(message = "์ฃผ์๋ ํ์์
๋๋ค.")
private String address;๐ ์ ํต์ ์ธ ์ํคํ
์ฒ๋ก, ๊ณ์ธต๋ณ๋ก ์ญํ ์ ๋ถ๋ฆฌํ์ฌ ๊ตฌ์ฑํ๋ ๋ฐฉ์
๐ ๋ํ์ ์ธ 3๊ณ์ธต ๊ตฌ์กฐ (Controller - Service - Repository)
๐ก ๊ตฌ์ฑ ์์
- ํ๋ ์ ํ ์ด์ ๊ณ์ธต (Presentation Layer) ๐จ
- ์ฌ์ฉ์์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ , ์๋ต์ ๋ฐํ (์:
Controller)
- ๋น์ฆ๋์ค ๊ณ์ธต (Business Layer) ๐ก
- ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋น (์:
Service)
- ํผ์์คํด์ค ๊ณ์ธต (Persistence Layer) ๐ฆ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ CRUD ์์
์ ๋ด๋น (์:
Repository)
- ๋๋ฉ์ธ ๊ณ์ธต (Domain Layer, ์ ํ์ ) ๐
- ๋๋ฉ์ธ ๋ชจ๋ธ๊ณผ ๋น์ฆ๋์ค ๊ท์น์ ์ ์
โ
์ฅ์
โ๏ธ ๊ตฌ์กฐ๊ฐ ๋จ์ํ๊ณ ์ดํดํ๊ธฐ ์ฌ์
โ๏ธ ์ญํ ์ด ๋ช
ํํ๊ฒ ๋ถ๋ฆฌ๋จ
โ ๋จ์
โ ๊ณ์ธต ๊ฐ ๊ฐํ ๊ฒฐํฉ์ด ๋ฐ์ํ ์ ์์
โ ๋ณํ์ ๋ํ ์ ์ฐ์ฑ์ด ๋ถ์กฑ
๐ ์ ํ๋ฆฌ์ผ์ด์
ํต์ฌ ๋ก์ง์ ์ธ๋ถ ์์คํ
๊ณผ ๋
๋ฆฝ์ ์ผ๋ก ์ ์งํ๋ ค๋ ์ํคํ
์ฒ
๐ "ํต์ฌ๊ณ ๋ "์ด ์๋๋ผ "ํต์ฌ๊ณ ๋ ๊ฒ์ฒ๋ผ ์๊ธด" ๊ตฌ์กฐ ๐คฃ
๐ก ๊ตฌ์ฑ ์์
- ๋๋ฉ์ธ (Domain) ๐ฏ
- ์ ํ๋ฆฌ์ผ์ด์ ์ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง (๋ ๋ฆฝ์ , ๋ณ๊ฒฝ์ด ์ ์)
- ์ด๋ํฐ (Adapters) ๐
- ๋๋ฉ์ธ์ ์ธ๋ถ ์์คํ
๊ณผ ์ฐ๊ฒฐํ๋ ์ญํ (์:
Controller,Repository,API Client)
- ํฌํธ (Ports) ๐
- ๋๋ฉ์ธ๊ณผ ์ด๋ํฐ ๊ฐ์ ์ธํฐํ์ด์ค (์:
UseCase,Repository Interface)
โ
์ฅ์
โ๏ธ ๋๋ฉ์ธ ๋ก์ง์ด ์ธ๋ถ ์์คํ
(DB, API ๋ฑ)์ ์ข
์๋์ง ์์
โ๏ธ ํ
์คํธ๊ฐ ์ฉ์ดํ๊ณ ํ์ฅ์ฑ์ด ๋ฐ์ด๋จ
โ ๋จ์
โ ์ด๊ธฐ ์ค๊ณ๊ฐ ๋ณต์กํ ์ ์์
โ ์ ์ ํ ์ธํฐํ์ด์ค ์ค๊ณ๊ฐ ํ์
| ์ํคํ ์ฒ | ์ฅ์ | ๋จ์ | ์ ์ฉ ์์ |
|---|---|---|---|
| ๋ ์ด์ด๋ ์ํคํ ์ฒ ๐๏ธ | ๊ตฌ์กฐ๊ฐ ๋จ์, ์ดํด ์ฌ์ | ๊ณ์ธต ๊ฐ ๊ฒฐํฉ๋ ๋์ | ์ ํต์ ์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ |
| ํต์ฌ๊ณ ๋ ์ํคํ ์ฒ ๐ ๏ธ | ๋๋ฉ์ธ ๋ ๋ฆฝ์ฑ, ์ ์ฐํจ | ์ค๊ณ ๋ณต์ก | ๋ง์ดํฌ๋ก์๋น์ค, DDD ๊ธฐ๋ฐ ํ๋ก์ ํธ |
๐ "๋ช ๋ น(Command)๊ณผ ์กฐํ(Query)์ ๋ถ๋ฆฌํ๋ ํจํด"
๐ก CQRS์ ํต์ฌ ๊ฐ๋
- Command (์ฐ๊ธฐ) โ๏ธ
- ๋ฐ์ดํฐ ๋ณ๊ฒฝ(์์ฑ, ์์ , ์ญ์ ) ์์ฒญ
- ๋ณดํต Event Sourcing๊ณผ ํจ๊ป ์ฌ์ฉ๋จ
- Query (์ฝ๊ธฐ) ๐
- ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ์์ฒญ
- ์ฝ๊ธฐ ์ ์ฉ ๋ชจ๋ธ๋ก ์ต์ ํ ๊ฐ๋ฅ
โ
์ฅ์
โ๏ธ ์ฝ๊ธฐ์ ์ฐ๊ธฐ์ ํ์ฅ์ฑ์ ๊ฐ๋ณ์ ์ผ๋ก ์กฐ์ ๊ฐ๋ฅ
โ๏ธ ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ฉ์ด
โ ๋จ์
โ ์ค๊ณ๊ฐ ๋ณต์กํ๋ฉฐ, ์ ์ง๋ณด์ ๋ถ๋ด ์ฆ๊ฐ
โ ๋ฐ์ดํฐ ๋๊ธฐํ ๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ
๐ ์ฌ์ฉ ์ฌ๋ก:
- ์ด๋ฒคํธ ๊ธฐ๋ฐ ์์คํ (ex: Kafka)
- ๊ณ ์ฑ๋ฅ ์ฝ๊ธฐ ์ต์ ํ๊ฐ ํ์ํ ์๋น์ค (ex: SNS ํ์๋ผ์ธ)
๐ ๊ฒฝ์์ด ๋ง์ง ์์ ๊ฒ์ผ๋ก ์์ํ๊ณ , ํธ๋์ญ์ ์ด ๋๋ ๋ ์ถฉ๋์ ๊ฒ์ฌํ๋ ๋ฐฉ์
๐ก ์๋ ๋ฐฉ์
- ๋ฐ์ดํฐ ์กฐํ ์ ๋ฒ์ (
version)์ ํจ๊ป ๊ฐ์ ธ์ด - ์ ๋ฐ์ดํธ ์ ํ์ฌ ๋ฒ์ ๊ณผ DB์ ๋ฒ์ ์ด ๊ฐ์์ง ํ์ธ
- ๋ค๋ฅด๋ฉด ์ถฉ๋ ๋ฐ์ โ ๊ฐฑ์ ์คํจ
โ
์ฅ์
โ๏ธ ๋ฝ์ ์ฌ์ฉํ์ง ์์ ์ฑ๋ฅ์ด ๋ฐ์ด๋จ
โ๏ธ ์ฝ๊ธฐ ์์
์ด ๋ง์ ํ๊ฒฝ์์ ์ ๋ฆฌ
โ ๋จ์
โ ์ถฉ๋์ด ๋ฐ์ํ๋ฉด ์ฌ์๋๊ฐ ํ์ํจ
โ ๋์ ์์ ์ด ๋ง์ ๊ฒฝ์ฐ ์คํจ ๊ฐ๋ฅ์ฑ ์ฆ๊ฐ
๐ ์ฌ์ฉ ์ฌ๋ก:
- ์ฝ๊ธฐ ๋น์ค์ด ๋์ ์๋น์ค (ex: ์ผํ๋ชฐ, ๊ฒ์ํ)
- JPA์์
@Version์ ์ฌ์ฉํ์ฌ ๊ตฌํ
@Version
private Long version;๐ ๊ฒฝ์์ด ๋ง์ ๊ฒ์ผ๋ก ์์ํ๊ณ , ํธ๋์ญ์ ์์ ์์ ์ ๋ฝ์ ๊ฑธ์ด๋ฒ๋ฆฌ๋ ๋ฐฉ์
๐ก ์๋ ๋ฐฉ์
- ๋ฐ์ดํฐ๋ฅผ ์์ ํ ๋ ์ฆ์ ๋ค๋ฅธ ํธ๋์ญ์ ์ ์ ๊ทผ์ ์ฐจ๋จ
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);| ๋ฝ ๋ฐฉ์ | ์ฅ์ | ๋จ์ | ์ฌ์ฉ ์์ |
|---|---|---|---|
| ๋๊ด์ ๋ฝ ๐ | ์ฑ๋ฅ ์ข์, ๋ฝ ์์ | ์ถฉ๋ ๋ฐ์ ์ ์ฌ์๋ ํ์ | ์ผํ๋ชฐ, ๊ฒ์ํ |
| ๋น๊ด์ ๋ฝ ๐ก | ์ถฉ๋ ๋ฐฉ์ง, ๋ฐ์ดํฐ ์์ ์ฑ | ์ฑ๋ฅ ์ ํ, ๋ฐ๋๋ฝ ๊ฐ๋ฅ | ์ํ ๊ณ์ข, ์ข์ ์์ฝ |
โ
๋ ์ด์ด๋ ์ํคํ
์ฒ: ๋จ์ํ๊ณ ์ดํดํ๊ธฐ ์ฌ์ด ๊ตฌ์กฐ์ง๋ง, ๊ณ์ธต ๊ฐ ๊ฒฐํฉ์ด ๋์
โ
ํต์ฌ๊ณ ๋ ์ํคํ
์ฒ: ๋๋ฉ์ธ ๋
๋ฆฝ์ฑ์ ๊ฐ์กฐํ์ฌ ํ์ฅ์ฑ๊ณผ ํ
์คํธ ์ฉ์ด์ฑ์ด ๋ฐ์ด๋จ
โ
CQRS: ์ฝ๊ธฐ/์ฐ๊ธฐ ๋ถ๋ฆฌ๋ก ์ฑ๋ฅ ์ต์ ํ ๊ฐ๋ฅํ์ง๋ง, ๋ณต์ก๋๊ฐ ์ฆ๊ฐํจ
โ
๋๊ด์ ๋ฝ: ์ถฉ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ์ฑ๋ฅ์ด ์ข์ง๋ง, ์ถฉ๋ ์ ์ฌ์๋ ํ์
โ
๋น๊ด์ ๋ฝ: ๋ฝ์ ๊ฑธ์ด ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ๋ณด์ฅํ์ง๋ง, ์ฑ๋ฅ ์ ํ ์ฐ๋ ค
๐ชฃ Dummy : ์๋ฌด๊ฒ๋ ํ์ง ์๋ ๊นกํต ๊ฐ์ฒด
๐ Fake : ๋จ์ํ ํํ๋ก ๋์ผํ ๊ธฐ๋ฅ ์ํํ์ง๋ง, ํ๋ก๋์ ์ฌ์ฉ์๋ ๋ถ์กฑํ ๊ฐ์ฒด (์: FakeRepository)
๐ Stub : ์์ฒญ๋ ๊ฒ์ ๋ํด ๋ฏธ๋ฆฌ ์ค๋นํ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ๋ ๊ฐ์ฒด, ๊ทธ ์ธ์๋ ์๋ตํ์ง ์์
- โ ์ํ ๊ฒ์ฆ ์ค์ฌ
๐ Spy : Stub์ด๋ฉด์ ํธ์ถ๋ ๋ด์ฉ์ ๊ธฐ๋กํ์ฌ ๋ณด์ฌ์ค ์ ์๋ ๊ฐ์ฒด
- ์ผ๋ถ๋ ์ค์ ๊ฐ์ฒด์ฒ๋ผ ๋์ํ๊ณ , ์ผ๋ถ๋ง Stubbing ๊ฐ๋ฅ
๐ญ Mock : ํ์์ ๋ํ ๊ธฐ๋๋ฅผ ๋ช ์ธํ๊ณ , ๊ทธ์ ๋ฐ๋ผ ๋์ํ๋๋ก ๋ง๋ค์ด์ง ๊ฐ์ฒด
- ๐ฏ ํ์ ๊ฒ์ฆ ์ค์ฌ
BDDMockito๋ Behavior-Driven Development(ํ์ ์ฃผ๋ ๊ฐ๋ฐ) ์คํ์ผ์ ํ
์คํธ๋ฅผ ์ง์ํ๋ Mockito API.
๊ธฐ์กด Mockito.when() ๋์ BDDMockito.given()์ ์ฌ์ฉํ์ฌ ๊ฐ๋
์ฑ์ด ์ข์ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑ ๊ฐ๋ฅ ๐โจ
- BDD ์คํ์ผ ์ง์ ๐๏ธ
- ๊ธฐ์กด์
when๋ณด๋คgiven์ ์ฌ์ฉํ์ฌ ํ ์คํธ์ ์๋๋ฅผ ๋ ๋ช ํํ๊ฒ ํํํ ์ ์์! - ์:
BDDMockito.given(service.callMethod()).willReturn("result");
- Mockito์ ๋์ผํ ๊ธฐ๋ฅ ์ ๊ณต โก
when().thenReturn()โgiven().willReturn()when().thenThrow()โgiven().willThrow()- ๊ธฐ๋ฅ์ ์ผ๋ก๋ ๊ฐ์ง๋ง BDD ๋ฐฉ์์ ๋ง๋ ํํ์ ์ฌ์ฉ!
- ๊ฐ๋ ์ฑ ํฅ์ ๐
- 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());
- ํ ์คํธ์ ๋ชฉ์ ์ ๋ ๋ช ํํ๊ฒ ๐ฏ
when()์ ์ผ๋ฐ์ ์ธ ๋ชจํน ํํ์ด๋ผ ํ ์คํธ์ ๋ชฉ์ ์ด ๋ชจํธํ ์ ์์given()์ ์ฌ์ฉํ๋ฉด ์ฌ์ ์กฐ๊ฑด(Given)์ด ๊ฐ์กฐ๋์ด ํ ์คํธ์ ์๋ ํ์ ์ด ์ฌ์
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 ํ์ฉ ๊ฐ๋ฅ (ํ์ง๋ง ์ ์คํ๊ฒ!)
โ ์ฅ์
- โ ํ ์คํธ๋ฅผ ํต๊ณผํด์ผ ๋ฌธ์ ์์ฑ ๐
- โ ํ๋ก๋์ ์ฝ๋์ ๋น์นจํฌ์ ๐
โ ๋จ์
- โ ์ฝ๋๋ ๋ง์ ๐๐
- โ ์ค์ ์ด๋ ค์ ๐ง๐ต
โ ์ฅ์
- โ ์ ์ฉ ์ฌ์ ๐ฏ
- โ ๋ฌธ์์์ ๋ฐ๋ก API ํธ์ถ ๊ฐ๋ฅ โก๐
โ ๋จ์
- โ ํ๋ก๋์ ์ฝ๋์ ์นจํฌ์ ๐๏ธ
- โ ํ ์คํธ์ ๋ฌด๊ดํ์ฌ ์ ๋ขฐ๋ ๋ฎ์ ์ ์์ ๐ค
- ๐ก ์ธ์ ์์ ์ ๊ฐ
- ๐ ์ ์ฑ ์ ๋ง๋ค๊ณ ์ธ๋ถ์ฌํญ์ ๋ฏธ๋ฃจ๋ ์์คํ ๊ฐ๋ฐ
๊ฐ๋ฐ ์ด๊ธฐ ๋จ๊ณ์์ ์ ํ์ ๋ฏธ๋ฃจ๊ธฐ:
- ์ด๊ธฐ์๋ Oracle์ธ์ง MySQL์ธ์ง ์ ํํ ํ์ ์์ ๐
- ์ด๊ธฐ์๋ Nginx์ธ์ง Apache์ธ์ง ์ ํํ ํ์ ์์ ๐
- ์ด๊ธฐ์๋ REST์ธ์ง GraphQL์ธ์ง ์ ํํ ํ์ ์์ ๐
- ์ด๊ธฐ์๋ Spring์ ์ ์ฉํ ํ์ ์์ ๐
๐ ๋๋ฉ์ธ ์ฐ์ ๊ฐ๋ฐ:
- ๐ก ๋๋ฉ์ธ์ด ๋จผ์ ๊ฐ๋ฐ๋์ด์ผ ํจ
๐ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ Spring Hexagonal Architecture
-
๐ ์์กด์ฑ ์ญ์ (Port-Adapter Pattern)
- โ๏ธ ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ: ์์คํ ์ ๊ฐ ๋ถ๋ถ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋๋ก ๋ถ๋ฆฌ
- ๐ ๊ณ ๋ฆฝํ: ์๋ก์ ์ํฅ์ ๋ฐ์ง ์๋๋ก ๊ฐ ๋ชจ๋์ ๋ ๋ฆฝ์ ์ผ๋ก ๋ง๋ฆ
- ๐ ์์กด์ฑ ์ญ์ : ์ธ๋ถ ์์คํ ์ ๋ณํ๊ฐ ๋ด๋ถ ์์คํ ์ ๋ฏธ์น๋ ์ํฅ์ ์ต์ํ
-
๐ฑ ์งํํ๋ ์ํคํ ์ฒ
- ๋ ์ด์ ํ์ํ์ง ์์ โ ๊ณ์ธต์ ์ ๊ฑฐํ๊ณ , ์์คํ ์ ๊ตฌ์กฐ๋ฅผ ์ ์ฐํ๊ฒ ์ ์ง
-
๊ตฌ์กฐ: ํ๋ ์ ํ ์ด์ โ ์๋น์ค โ ๋๋ฉ์ธ โ ๋ฆฌํฌ์งํ ๋ฆฌ(DB) ์์ผ๋ก ์์ง ๊ณ์ธต ๊ตฌ์ฑ
-
๋ฌธ์ ์ :
- ํ์ ๊ณ์ธต(ํนํ DB)์ ์์กดํ๊ฒ ๋๋ฉฐ, DB ์ค์ฌ์ ์ฌ๊ณ ๋ฐฉ์์ ๊ฐ๊ฒ ๋จ
- ์ํฅ์ ์ ๊ทผ ์: JPA(Entity)๋ถํฐ ์ค๊ณํ๊ฒ ๋๋ ๊ฒฝํฅ
- ํํฅ์ ์ ๊ทผ ์: ํ๋ ์์ํฌ(Spring, MVC ๋ฑ)๋ถํฐ ๊ณ ๋ คํ๊ฒ ๋จ
-
โ ๋น์ฆ๋์ค ๋ก์ง ์ค์ฌ ์ค๊ณ๊ฐ ์ด๋ ค์์ง
-
๊ตฌ์กฐ: ๋๋ฉ์ธ(ํต์ฌ ๋ก์ง)์ ์ค์ฌ์ผ๋ก, ํฌํธ(์ธํฐํ์ด์ค)์ ์ด๋ํฐ(ํ๋ ์์ํฌ, DB, UI ๋ฑ)๊ฐ ๋ฐ๊นฅ์ ์์น
-
์ฅ์ :
- ์ํฅ์ ์ ๊ทผ์ด ์์ฐ์ค๋ฌ์
- ํ๋ ์์ํฌ, DB, UI ๋ฑ ์ธ๋ถ ์์๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๋ค๋ฃฐ ์ ์์ด ํ ์คํธ ์ฉ์ด์ฑ ๋ฐ ์ ์ง๋ณด์์ฑ ํฅ์
- ๋๋ฉ์ธ ๋ก์ง์ ์ค์ฌ์ผ๋ก ์ฌ๊ณ ํ ์ ์์
- ํ ์คํธํ๊ธฐ ์ด๋ ค์ด ์์(GUI, DB ๋ฑ)๋ฅผ ๋ถ๋ฆฌ๋ ํ๋ธ ๊ฐ์ฒด๋ก ๋ฐ๋ก ๋ถ๋ฆฌ
- ๋ณธ์ง์ ์ธ ๋ก์ง(๋น์ฆ๋์ค ๋ก์ง)๊ณผ ํ๋ธ ๊ฐ์ฒด(ํ ์คํธ ์ด๋ ค์ด ์์)๋ฅผ ๋ช ํํ๊ฒ ๊ตฌ๋ถ
- "๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ฐ๊พผ๋ค๊ณ ๊ณ์ฐ ๋ก์ง์ด ๋ณ๊ฒฝ๋๋ฉด ์ ๋๋ค"
- "ํ๋ ์์ํฌ๋ฅผ ๋ฐ๊พผ๋ค๊ณ ๊ณ์ฐ ๋ก์ง์ด ๋ณ๊ฒฝ๋๋ฉด ์ ๋๋ค"
- "์ฌ์ง์ด ์ธ์ด๋ฅผ ๋ฐ๊ฟ๋ ๊ณ์ฐ ๋ก์ง์ ์ ์ง๋์ด์ผ ํ๋ค"
- ์ค๊ณ์ ํต์ฌ์ ์์กด์ฑ์ ๋ฐฉํฅ ์ ์ด์ ๊ณ ๋ฆฝ์ฑ ์ ์ง
- ํ๋ธ ๊ฐ์ฒด๋ ์ธ๋ถ ์์กด์ฑ์ด ํฌ๋ฏ๋ก, ํ ์คํธ์์ ๋ฐฐ์ ํ๊ฑฐ๋ Mocking ๋์์ผ๋ก ์ผ์
์ด๋ํฐ๋ ์ธ๋ถ ์์คํ (์น, DB, ๋ฉ์์ง ๋ฑ)๊ณผ ๋ด๋ถ ์ ํ๋ฆฌ์ผ์ด์ (๋๋ฉ์ธ/์ ์ค์ผ์ด์ค) ์ฌ์ด์ ์ ยท์ถ๋ ฅ ์ฐ๊ฒฐ ์ญํ ์ ํฉ๋๋ค.
- HTTP ์์ฒญ์ ์๋ฐ ๊ฐ์ฒด๋ก ๋ณํ (ex. JSON โ DTO)
- ๊ถํ ๊ฒ์ฌ
- ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฆ
- ์น DTO๋ฅผ โ ์ ์ค์ผ์ด์ค ์ ๋ ฅ ๋ชจ๋ธ๋ก ๋ณํ
- ์ ์ค์ผ์ด์ค ํธ์ถ
- ์ ์ค์ผ์ด์ค ๊ฒฐ๊ณผ๋ฅผ HTTP ์๋ต ํ์์ผ๋ก ๋ณํ
- HTTP ์๋ต ๋ฐํ
โ ๏ธ ์ฃผ์์ฌํญ ์น ์ด๋ํฐ์์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ค๋ณต ๊ตฌํํ ํ์๋ ์์ต๋๋ค. ๋ค๋ง, โ์น ์ ๋ ฅ DTO โ ์ ์ค์ผ์ด์ค ์ ๋ ฅ ๋ชจ๋ธโ๋ก ๋ณํ์ด ๊ฐ๋ฅํ์ง๋ง ๊ฒ์ฆํ๋ฉด ์ถฉ๋ถํฉ๋๋ค.
- ์ ์ค์ผ์ด์ค ๋๋ ๋๋ฉ์ธ์ผ๋ก๋ถํฐ ์ ๋ ฅ์ ๋ฐ์
- ์ด๋ฅผ DB ํฌ๋งท(JPA Entity)๋ก ๋งคํ
- DB์ ์ ์ฅํ๊ฑฐ๋ ์กฐํ
- ๊ฒฐ๊ณผ๋ฅผ ๋๋ฉ์ธ ๋ชจ๋ธ ๋๋ ์ ํ๋ฆฌ์ผ์ด์ ํฌ๋งท์ผ๋ก ๋ณํ
- ์ ์ค์ผ์ด์ค ๋๋ ๋๋ฉ์ธ์ผ๋ก ์ถ๋ ฅ ๋ฐํ
๐ก ์๋ฐ์์๋ ๋ณดํต JPA๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ์ ๋ ฅ ๋ชจ๋ธ์ JPA ์ํฐํฐ๋ก ๋งคํํ๊ณ , DB ๊ฒฐ๊ณผ๋ฅผ ๋๋ฉ์ธ ๋ชจ๋ธ ๋๋ DTO๋ก ๋ค์ ๋ณํํ๋ ๊ตฌ์กฐ๊ฐ ์ผ๋ฐ์ ์ ๋๋ค.
- ๋๋ฉ์ธ ๊ท์น์ ๋ฐํ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ํ๋ฆ์ ์ ์
- ๋น์ฆ๋์ค ๋ก์ง์ ์กฐํฉํ๊ณ ์คํํ๋ ๋จ์
- ์ธ๋ถ ์์กด์ฑ(DB, UI ๋ฑ)์ ๋ํด ๋ฌด์ง(็ก็ฅ)ํด์ผ ํ๋ฉฐ, ์์ํ ์๋ฐ ์ฝ๋๋ก ์ ์ง๋จ
| ์์ | ์ญํ ์์ฝ |
|---|---|
| Input ์ด๋ํฐ | ์ธ๋ถ ์์ฒญ(HTTP ๋ฑ)์ ์ ์ค์ผ์ด์ค ์ ๋ ฅ ๋ชจ๋ธ๋ก ๋ณํ ํ ํธ์ถ |
| ์ ์ค์ผ์ด์ค | ๋น์ฆ๋์ค ๋ก์ง ์คํ (๋๋ฉ์ธ ์กฐํฉ ๋ฐ ํ๋ฆ ์ ์ด) |
| Output ์ด๋ํฐ | ์ ์ค์ผ์ด์ค ์์ฒญ์ DB ํ์(JPA ๋ฑ)์ผ๋ก ๋ณํํ๊ณ ์ฒ๋ฆฌ |





