๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

25-03-18

๐Ÿ“Œ Daily Reportโ€‹

https://github.com/ssginc-be/DOKI/issues/121


๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์ƒํ™ฉโ€‹

์ปคํ”ผ๊ฐ€ ์ž˜ ์•ˆ๋ฐ›๋Š”๋‹ค. ์ƒท์ถ”๊ฐ€ ๊ฐ€์ง€๊ณ ๋Š” ์•ˆ๋˜๊ณ  ์ฝœ๋“œ๋ธŒ๋ฃจ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ๊น”๊ณ  ๊ฐ€์•ผ ๋˜๋Š”๋ฐ

๋ฐ”๋‚˜ํ”„๋ ˆ์†Œ๋Š” ์ฝœ๋“œ๋ธŒ๋ฃจ๊ฐ€ ์ข€ ์—ฐํ•จ... ๋ฌผํƒ„๊ฑฐ๊ฐ™๋‹ค.


๊ทธ๋ฆฌ๊ณ  ์–ด์ œ ๋ฉ˜ํ† ๋ง ๋ฐ›์•˜์„ ๋•Œ WBS๋Œ€๋กœ๋งŒ ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ๋ง์”€ํ•˜์…”์„œ ์ž˜ ๋งˆ๋ฌด๋ฆฌ๋งŒ ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค ์‹ถ์€๋ฐ

๊ทธ ๋งˆ๋ฌด๋ฆฌ๋ผ๋Š”๊ฒŒ ์ฐธ... ์—ฌ๊ธฐ์— ๋‹ค ์“ธ ์ˆ˜ ์—†์ง€๋งŒ, ์–ด๋ ค์šด ๊ฒƒ ๊ฐ™๋‹ค.

๋˜ํ•œ NELO๋ผ๋Š” ๋„ค์ด๋ฒ„ ๋‚ด๋ถ€ ๋กœ๊น… ์‹œ์Šคํ…œ์— ๋Œ€ํ•ด ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค. ๋ฉ˜ํ† ๋‹˜๊ป˜์„  ๋กœ๊ทธ๋ฅผ ์—ฌ๊ธฐ๋กœ ๋‹ค ๋ณด๋‚ด์„œ ๊ด€๋ฆฌํ•œ๋‹ค๊ณ  ํ•˜์…จ๋‹ค.

์‹œ์Šคํ…œ์ด ๋„ˆ๋ฌด ๋ง›์žˆ๊ฒŒ ์ƒ๊ฒผ๋”๋ผ. ๋„ค์ด๋ฒ„์— ๊ทธ๋Ÿฐ ์ž์ฒด ๋ฐฑ์˜คํ”ผ์Šค๋“ค์ด ๊ฝค ์žˆ์—ˆ๋‹ค.

๋ญ”๊ฐ€, ๋‚ด๊ฐ€ ํด๋ผ์šฐ๋“œ์— ๊ด€์‹ฌ ๊ฐ€์ง„๊ฒƒ๋„ ๊ฐ€์ƒํ™” ๊ธฐ์ˆ  ์ž์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๊ทธ๊ฑธ ์ด์šฉํ•œ ์œ ํ‹ธ์„ฑ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์ด๋“ฏ์ด, ๋ฐฑ์˜คํ”ผ์Šค๋ž‘ ๋‚˜๋ž‘ ์ž˜ ๋งž๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค๊ธฐ๋„ ํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฌธ์„œํ™”์— ๋Œ€ํ•ด์„œ๋„ ์Šค๋ชฐํ†ก ํ–ˆ๋Š”๋ฐ, ๋‚ด๊ฐ€ ๋ฌธ์„œํ™” ๋„ˆ๋ฌด ๊ท€์ฐฎ๋‹ค, ์ด๊ฑธ ์ž๋™ํ™”ํ•˜๋ฉด ์ข‹์ง€ ์•Š์„๊นŒ์š”? ์ด์Šˆ ์“ฐ๋ฉด ๋”ฑ ์—‘์…€์— ๋ฐ˜์˜๋˜๊ณ  ๊ทธ๋Ÿฐ๊ฑฐ.. ํ•˜๋‹ˆ๊นŒ ๋ฉ˜ํ† ๋‹˜์ด ๋ง‰ ์›ƒ์œผ์…จ๋‹ค.

์•„์ด๋””์–ด๊ฐ€ ์žฌ๋ฐŒ์–ด์„œ ์›ƒ์œผ์‹ ๊ฑด๊ฐ€ 'ใ…'? ๊ทธ๋ž˜๋„ ์›ƒ์œผ์…จ์œผ๋‹ˆ๊นŒ ๋‹คํ–‰์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.


๋ฉ˜ํ† ๋‹˜์ด ๋‘ ๋ถ„์ด์…จ๋Š”๋ฐ ์„œ๋กœ ๋น„์Šทํ•˜๋ฉด์„œ๋„ ๋‹ค๋ฅธ ๋ฐฐ๊ฒฝ์„ ๊ฐ€์ง€๊ณ  ๊ณ„์‹  ๊ฒƒ์ด ๋Š๊ปด์กŒ๋‹ค. ์‹œ์ž‘์€ ๋น„์Šทํ•˜๊ณ  ์ปค๋ฆฌ์–ดํŒจ์Šค๋Š” ๋‹ค๋ฅด๋‹ฌ๊นŒ...

ํ•œ ๋ถ„์€ ํ˜„์‹ค์ ์œผ๋กœ ๋ง์”€ํ•ด์ฃผ์‹œ๋Š” ๋ถ„์ด์—ˆ๋Š”๋ฐ ์•„๋ฒ„์ง€๋ž‘ ๋˜‘๊ฐ™์ด ๋ง์”€ํ•˜์…”์„œ ์‹ ๊ธฐํ–ˆ๋‹ค. ๋ถ€์ •์ ์œผ๋กœ ๋ง์”€ํ•˜์‹œ์ง€๋งŒ ๊ธฐ๋ถ„์ด ํ•˜๋‚˜๋„ ์•ˆ๋‚˜์˜๊ณ  ๋Š˜์ƒ ๋“ฃ๋˜ ๋Š๋‚Œ? ๋ฌด์Šจ ๋งฅ๋ฝ์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ๋ง์”€ํ•˜์‹œ๋Š”์ง€ ๋„ˆ๋ฌด ์ดํ•ด๊ฐ€ ์ž˜ ๋˜๋”๋ผ.

๋‹ค๋ฅธ ํ•œ ๋ถ„์€ ํ˜„์‹ค์ ์ธ ์–˜๊ธฐ๋ณด๋‹ค๋Š” ๊ธฐ์ˆ ์˜ ๋‹น์œ„์„ฑ์ด๋‚˜ ์ž๊ธฐ ์ฃผ๊ด€ ๋šœ๋ ทํ•œ ์กฐ์–ธ์„ ํ•ด์ฃผ์‹œ๋Š” ๋ถ„์ด์—ˆ๋‹ค. ๋‚ ์นด๋กญ๊ฒŒ ๋Š๊ปด์งˆ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ๋‚˜๋„ ๊ฐœ๋ฐœ์— ์žˆ์–ด ๊ทธ๋Ÿฐ ์ž์„ธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์ ์€ ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๋‹ค์‹œ ๋งํ•˜๋ฉด, ์„ฑํ–ฅ์ด ๋งž๋Š”๋‹ค.

๊ทธ๋Ÿฐ ํฅ๋ฏธ๋กœ์šด ๋‘ ๋ถ„์˜ ๋ชจ์Šต์€, ๋‚ด ๋ฏธ๋ž˜์˜ ๋ฐฉํ–ฅ์„ฑ์„ ์ƒ๊ฐํ•ด๋ณด๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋˜์—ˆ๋‹ค. ๋‹น์žฅ 1๋…„ ํ›„๋ถ€ํ„ฐ 10๋…„ ํ›„๊นŒ์ง€.


๐Ÿ“Œ @Mock vs @Autowiredโ€‹

Service๋ฅผ ์„ ์–ธํ• ๋•Œ ์œ„ 2๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐ๊ฐ ๋™์ž‘๋ฐฉ์‹์ด ๋‹ค๋ฅธ ๊ฒƒ ๊ฐ™๋‹ค.

๋Š๋‚Œ ์ƒ์œผ๋กœ @Mock์€ ๊ทธ๋ƒฅ ๊ฐ€์งœ ๊ป๋ฐ๊ธฐ๊ณ  ์ง„์งœ ์ž‘๋™๋˜๋ ค๋ฉด @Autowired๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

ES ์ธ๋ฑ์‹ฑ๋„ mock์ด ๊ฐ€๋Šฅํ•œ๊ฐ€? ์ฐพ์•„๋ณด๋‹ˆ ์ด ๊ฒฝ์šฐ์—” ES ์„œ๋ฒ„๋ฅผ ๊ฐ€์ƒ์œผ๋กœ ์„ ์–ธํ•ด์„œ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค๋งŒ... ์ง€๊ธˆ ์ƒํ™ฉ์—์„  ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ํฐ ๊ฒƒ ๊ฐ™์€๋ฐ์š” ใ…Ž

์ผ๋‹จ์€ ์Šคํ”„๋ง ์‹คํ–‰์‹œ mock data์— ๋งž์ถ”์–ด ES ์ธ๋ฑ์‹ฑ๋„ ์ดˆ๊ธฐํ™”๋˜๋„๋ก ํ•ด๋†“์•˜๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ์—์„œ ์‹ค์ œ ๋กœ์ง์ด ๊ตด๋Ÿฌ๊ฐ€๋„ ์ƒ๊ด€์ด ์—†์–ด์„œ @Autowired๋ฅผ ์ฑ„ํƒํ–ˆ๋‹ค.

@SpringBootTest
@AutoConfigureMockMvc
public class ReserveRestControllerV1Test {
/*
V1 ์˜ˆ์•ฝ ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ
*/

@Autowired
private ReservationEntryRepository reRepo;
@Autowired
private TestReserveService testReserveService; // ์‹ค์ œ service์— mock ๊ฐ์ฒด ์ฃผ์ž…

private int RESERVATION_TRIAL;

private Long TARGET_ENTRY_ID;

@BeforeEach
void setUp() {
// Given: ๊ณตํ†ต ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ
// ๊ณต๊ฒฉํ•  ์—”ํŠธ๋ฆฌ๋Š” data.sql ์ฐธ๊ณ 
RESERVATION_TRIAL = 100000;
TARGET_ENTRY_ID = 3L;
}

// 1. ๋‹จ์ˆœ @Transactional๋งŒ ์„ค์ •ํ•œ ์˜ˆ์•ฝ ๋™์ž‘ ํ…Œ์ŠคํŠธ
@DisplayName("@Transactional ์˜ˆ์•ฝ ํ…Œ์ŠคํŠธ")
@Test
//@Sql("/data.sql") // resources/data.sql ์‹คํ–‰ ํ•„์ˆ˜
public void testWithSimpleTransaction() throws InterruptedException {
// ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ: 2๊ฐœ์˜ ์Šค๋ ˆ๋“œ์—์„œ ์˜ˆ์•ฝ ์‹œ๋„
Runnable reservationTask1 = () -> {
for (int i = 0; i < RESERVATION_TRIAL/2; ++i) {
ReserveRequestDto reserveRequestDto = ReserveRequestDto.builder()
.storeId(1L)
.entryId(TARGET_ENTRY_ID)
.memberCode((long)(100000 + i)) // ๊ฐ€์ƒ memberCode ์„ค์ •: 100000 ~ 149999
.reservedDateTime(LocalDateTime.parse("2025-02-25 14:30", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))
.headcount(1)
.build();

testReserveService.testWithSimpleTransaction(reserveRequestDto);
}
};
Runnable reservationTask2 = () -> {
for (int i = RESERVATION_TRIAL/2; i < RESERVATION_TRIAL; ++i) {
ReserveRequestDto reserveRequestDto = ReserveRequestDto.builder()
.storeId(1L)
.entryId(TARGET_ENTRY_ID)
.memberCode((long)(100000 + i)) // ๊ฐ€์ƒ memberCode ์„ค์ •: 150000 ~ 199999
.reservedDateTime(LocalDateTime.parse("2025-02-25 14:30", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))
.headcount(1)
.build();

testReserveService.testWithSimpleTransaction(reserveRequestDto);
}
};

// ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ์˜ˆ์•ฝ ์‹œ๋„
Thread thread1 = new Thread(reservationTask1);
Thread thread2 = new Thread(reservationTask2);

thread1.start();
thread2.start();
thread1.join();
thread2.join();

// ์˜ˆ์•ฝ ์ •์› ์ดˆ๊ณผ ์—ฌ๋ถ€ ๊ฒ€์ฆ
ReservationEntry entry = reRepo.findById(TARGET_ENTRY_ID).get();
assertSame(entry.getReservedCount(), entry.getCapacity()); // ์ตœ๋Œ€ ์ •์› ์ดˆ๊ณผ ์—ฌ๋ถ€ ํ™•์ธ
assertEquals("CLOSED", entry.getEntryStatus().toString()); // ์—”ํŠธ๋ฆฌ๊ฐ€ ๋‹ซํ˜”๋Š”์ง€ ํ™•์ธ
}
}

์ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„, @Autowired ๋ง๊ณ  @Mock์œผ๋กœ ๋‹ฌ๋ฉด data.sql๋กœ DB์— INSERT๊ฐ€ ๋˜์—ˆ๋“  ๋ง๋“  ์—”ํŠธ๋ฆฌ ID 3๋ฒˆ์ด ์žˆ๋‹ค๋Š”๊ฑธ ์ธ์‹์„ ๋ชปํ•œ๋‹ค. ์•„์˜ˆ ๋ถ„๋ฆฌ๊ฐ€ ๋˜์–ด์žˆ๋Š”๋“ฏ.

@Autowired๋„ ๋ชปํ•ด์ฃผ๋Š” ๊ฒƒโ€‹

ํ…Œ์ŠคํŠธ์‹คํŒจ๋กœ๊ทธ

ํ„ฐ์ง„๋ถ€๋ถ„

๊ทธ๋Ÿฌํ•˜๋‹ค. ์ด์šฉ์ž ๊ณ„์ •๋„ 10๋งŒ๊ฐœ ๋งŒ๋“ค์–ด ๋„ฃ์œผ๋ ด ^^