[๐Ÿ“ฐ ์œ„ํด๋ฆฌํŽ˜์ดํผ] ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋ฒ”์œ„์™€ ์ฑ…์ž„ & Mockito

han91ยท2026๋…„ 4์›” 5์ผ

[์œ„ํด๋ฆฌํŽ˜์ดํผ]

๋ชฉ๋ก ๋ณด๊ธฐ
10/12

๐Ÿ“Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ„์ธต๋ณ„ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ

๐Ÿง ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์ด๋ž€?

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •

๋‹จ์ˆœํžˆ null ์ฒดํฌ๋งŒ ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ

  • ์š”์ฒญ ํ˜•์‹์ด ๋งž๋Š”์ง€
  • ๊ฐ’์˜ ๋ฒ”์œ„๊ฐ€ ์ ์ ˆํ•œ์ง€
  • ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ๋งŒ์กฑํ•˜๋Š”์ง€

๐Ÿ‘‰ ๋‹ค์–‘ํ•œ ์ˆ˜์ค€์˜ ๊ฒ€์ฆ์ด ์กด์žฌํ•จ

๐Ÿค” ์™œ ๊ณ„์ธต๋ณ„๋กœ ๋‚˜๋ˆ ์•ผ ํ• ๊นŒ?

ํ•œ ๊ตฐ๋ฐ์—์„œ๋งŒ ๊ฒ€์ฆํ•˜๋ฉด?

๐Ÿ‘‰ ๋‹ค๋ฅธ ๊ฒฝ๋กœ์—์„œ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ฐฉ์–ด ๋ชป ํ•จ

๋ฐ˜๋Œ€๋กœ ๋ชจ๋“  ๊ณ„์ธต์—์„œ ๊ฒ€์ฆํ•˜๋ฉด?

๐Ÿ‘‰ ์ค‘๋ณต ์ฝ”๋“œ ์ฆ๊ฐ€
๐Ÿ‘‰ ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€

๐Ÿ‘‰ ๊ทธ๋ž˜์„œ ์ฑ…์ž„์„ ๋‚˜๋ˆ ์•ผ ํ•จ

โš ๏ธ ๊ณ„์ธต๋ณ„ ์ฑ…์ž„

1๏ธโƒฃ Controller (์ž…๋ ฅ ํ˜•์‹ ๊ฒ€์ฆ)

"์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋‚ธ ๊ฐ’์ด ์ •์ƒ์ ์ธ ํ˜•ํƒœ์ธ๊ฐ€?"

public record CreateUserRequestDTO(
    @NotBlank String email,
    @NotBlank String username
) {}

โœ”๏ธ ์—ญํ• 

null, ๊ณต๋ฐฑ ์ฒดํฌ
ํ˜•์‹ ๊ฒ€์ฆ (์ด๋ฉ”์ผ, ๊ธธ์ด ๋“ฑ)

๐Ÿ‘‰ ๊ฐ€์žฅ ๋ฐ”๊นฅ์—์„œ 1์ฐจ ํ•„ํ„ฐ๋ง

2๏ธโƒฃ Service (๋น„์ฆˆ๋‹ˆ์Šค ๊ฒ€์ฆ)

"์ด ๊ฐ’์ด ์„œ๋น„์Šค ๊ทœ์น™์— ๋งž๋Š”๊ฐ€?"

if (userRepository.existsByEmail(email)) {
    throw new DuplicateEmailException();
}

โœ”๏ธ ์—ญํ• 

์ค‘๋ณต ๊ฒ€์‚ฌ
์ƒํƒœ ๊ฒ€์ฆ
๊ถŒํ•œ ์ฒดํฌ

๐Ÿ‘‰ "์ด๊ฒŒ ๊ฐ€๋Šฅํ•œ ์š”์ฒญ์ธ๊ฐ€?"๋ฅผ ํŒ๋‹จ

3๏ธโƒฃ Domain / Entity (๋ถˆ๋ณ€์„ฑ ๋ณด์žฅ)

"์ด ๊ฐ์ฒด๋Š” ํ•ญ์ƒ ์œ ํšจํ•œ ์ƒํƒœ์ธ๊ฐ€?"

public void changeName(String name) {
    if (name == null || name.isBlank()) {
        throw new IllegalArgumentException();
    }
    this.name = name;
}

โœ”๏ธ ์—ญํ• 

๊ฐ์ฒด ์ž์ฒด์˜ ์œ ํšจ์„ฑ ์œ ์ง€
์ž˜๋ชป๋œ ์ƒํƒœ ์ƒ์„ฑ ๋ฐฉ์ง€

๐Ÿ‘‰ ๋งˆ์ง€๋ง‰ ๋ฐฉ์–ด์„ 

๐Ÿ”ฅ ์ค‘๋ณต ๊ฒ€์ฆ ์—†์ด ์•ˆ์ •์„ฑ ํ™•๋ณดํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ‘‰ ํ•ต์‹ฌ ์ „๋žต

Controller โ†’ "ํ˜•์‹ ๊ฒ€์ฆ"
Service โ†’ "๋น„์ฆˆ๋‹ˆ์Šค ๊ฒ€์ฆ"
Entity โ†’ "๋ถˆ๋ณ€์„ฑ ๋ณด์žฅ"

๐Ÿ‘‰ ๊ฐ™์€ ๊ฒ€์ฆ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•˜์ง€ ์•Š๋Š”๋‹ค

์˜ˆ์‹œ โŒ (์ž˜๋ชป๋œ ๊ตฌ์กฐ)

Controller์—์„œ null ์ฒดํฌ
Service์—์„œ๋„ null ์ฒดํฌ
Entity์—์„œ๋„ null ์ฒดํฌ

๐Ÿ‘‰ ์™„์ „ ์ค‘๋ณต

์˜ˆ์‹œ โญ•

Controller: @NotBlank
Service: ์ค‘๋ณต ์ด๋ฉ”์ผ ๊ฒ€์‚ฌ
Entity: ์ƒํƒœ ๋ณด์žฅ

๐Ÿ‘‰ ์—ญํ•  ๋ถ„๋ฆฌ ์™„๋ฃŒ

โš–๏ธ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„

1๏ธโƒฃ ์ค‘๋ณต ์ œ๊ฑฐ vs ์•ˆ์ •์„ฑ

๊ฒ€์ฆ์„ ์ค„์ด๋ฉด โ†’ ์ฝ”๋“œ ๊น”๋” ๐Ÿ‘
๊ฒ€์ฆ์„ ๋Š˜๋ฆฌ๋ฉด โ†’ ์•ˆ์ „์„ฑ โ†‘ ๐Ÿ‘

๐Ÿ‘‰ ์„ ํƒ ํ•„์š”

2๏ธโƒฃ Entity ๊ฒ€์ฆ ๊ฐ•๋„

๊ฐ•ํ•˜๊ฒŒ ํ•˜๋ฉด โ†’ ์•ˆ์ „ ๐Ÿ‘
๋„ˆ๋ฌด ๊ฐ•ํ•˜๋ฉด โ†’ ์œ ์—ฐ์„ฑ โ†“ ๐Ÿ‘Ž

๐Ÿ‘‰ DTO โ†’ Service โ†’ Entity ํ๋ฆ„ ์ค‘์š”

3๏ธโƒฃ ์„ฑ๋Šฅ vs ๋ฐฉ์–ด

๊ฒ€์ฆ ๋งŽ์œผ๋ฉด โ†’ ์•ˆ์ „ ๐Ÿ‘
ํ•˜์ง€๋งŒ โ†’ ๋น„์šฉ ์ฆ๊ฐ€ ๐Ÿ‘Ž

๐Ÿ‘‰ "ํ•„์š”ํ•œ ๋งŒํผ๋งŒ"

๐Ÿง  ์ •๋ฆฌ

์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ = ๊ณ„์ธต๋ณ„ ์—ญํ•  ๋ถ„๋ฆฌ

Controller โ†’ ํ˜•์‹ ๊ฒ€์ฆ
Service โ†’ ๋น„์ฆˆ๋‹ˆ์Šค ๊ฒ€์ฆ
Entity โ†’ ์ƒํƒœ ๋ณด์žฅ

ํ•ต์‹ฌ ๐Ÿ‘‰ ์ค‘๋ณต ์—†์ด ์ฑ…์ž„ ๋ถ„๋ฆฌ
ํŠธ๋ ˆ์ด๋“œ์˜คํ”„ ๐Ÿ‘‰ ์•ˆ์ •์„ฑ vs ๋ณต์žก๋„


๐Ÿ“Œ Mockito์˜ Mock, Stub, Spy

๐Ÿง Mockito๋ž€?

ํ…Œ์ŠคํŠธ์—์„œ ์‹ค์ œ ๊ฐ์ฒด ๋Œ€์‹  ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿ‘‰ ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์คŒ

๐Ÿค” ์™œ ํ•„์š”ํ•œ๊ฐ€?

์˜ˆ๋ฅผ ๋“ค์–ด Service๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ

DB ์ ‘๊ทผ
API ํ˜ธ์ถœ

๐Ÿ‘‰ ์ด๋Ÿฐ ๊ฒƒ๊นŒ์ง€ ๊ฐ™์ด ํ…Œ์ŠคํŠธํ•˜๋ฉด

๋А๋ฆผ
๋ถˆ์•ˆ์ •
ํ…Œ์ŠคํŠธ ์–ด๋ ค์›€

๐Ÿ‘‰ ๊ทธ๋ž˜์„œ ๊ฐ€์งœ ๊ฐ์ฒด ์‚ฌ์šฉ

โš ๏ธ ๊ฐœ๋… ์ •๋ฆฌ

1๏ธโƒฃ Mock

ํ–‰๋™์„ ์ง์ ‘ ์ •์˜ํ•˜๋Š” ๊ฐ€์งœ ๊ฐ์ฒด

UserRepository userRepository = mock(UserRepository.class);

when(userRepository.findById(1L))
    .thenReturn(Optional.of(user));

โœ”๏ธ ํŠน์ง•

์•„๋ฌด ๋™์ž‘๋„ ์•ˆ ํ•จ
์šฐ๋ฆฌ๊ฐ€ ์ •์˜ํ•œ ๋Œ€๋กœ๋งŒ ๋™์ž‘

๐Ÿ‘‰ ์™„์ „ํ•œ ๊ฐ€์งœ

2๏ธโƒฃ Stub

ํŠน์ • ์ž…๋ ฅ์— ๋Œ€ํ•ด ๋ฏธ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ •ํ•ด๋‘” ๊ฒƒ

when(userRepository.existsByEmail("test@test.com"))
    .thenReturn(true);

โœ”๏ธ ํŠน์ง•

"์ด ๊ฐ’ ๋„ฃ์œผ๋ฉด ์ด ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜"

๐Ÿ‘‰ Mock์˜ ์‚ฌ์šฉ ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜

3๏ธโƒฃ Spy

์‹ค์ œ ๊ฐ์ฒด + ์ผ๋ถ€๋งŒ ๊ฐ€์งœ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ

List<String> list = spy(new ArrayList<>());

list.add("A"); // ์‹ค์ œ ๋™์ž‘

doReturn(100).when(list).size(); // ์ผ๋ถ€๋งŒ ์กฐ์ž‘

โœ”๏ธ ํŠน์ง•

์‹ค์ œ ๋กœ์ง ์‹คํ–‰๋จ
์ผ๋ถ€๋งŒ override ๊ฐ€๋Šฅ

๐Ÿ‘‰ ๋ฐ˜์ฏค ์ง„์งœ

๐Ÿ”ฅ ์–ธ์ œ ๋ฌด์—‡์„ ์จ์•ผ ํ• ๊นŒ?

1๏ธโƒฃ Mock

๐Ÿ‘‰ ์™ธ๋ถ€ ์˜์กด์„ฑ ์ œ๊ฑฐํ•  ๋•Œ

์˜ˆ์‹œ

Repository
API Client
when(userRepository.findById(1L)).thenReturn(user);

๐Ÿ‘‰ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ

2๏ธโƒฃ Stub

๐Ÿ‘‰ "๊ฒฐ๊ณผ๋งŒ ํ•„์š”ํ•  ๋•Œ"

์˜ˆ์‹œ

์กฐ๊ฑด ๋ถ„๊ธฐ ํ…Œ์ŠคํŠธ

when(repo.existsByEmail(email)).thenReturn(true);

๐Ÿ‘‰ ๋กœ์ง ํ๋ฆ„ ๊ฒ€์ฆ

3๏ธโƒฃ Spy

๐Ÿ‘‰ ์ผ๋ถ€๋งŒ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ

์˜ˆ์‹œ

๋ณต์žกํ•œ ๊ฐ์ฒด
์‹ค์ œ ๋กœ์ง ์ผ๋ถ€ ์œ ์ง€

๐Ÿ‘‰ ํ•˜์ง€๋งŒ

โš ๏ธ ๋‚จ์šฉ ๊ธˆ์ง€
โ†’ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ณต์žกํ•ด์ง

โš–๏ธ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„

1๏ธโƒฃ Mock ๋‚จ์šฉ

๋น ๋ฅด๊ณ  ํŽธํ•จ ๐Ÿ‘
ํ•˜์ง€๋งŒ ์‹ค์ œ์™€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ ๐Ÿ‘Ž

2๏ธโƒฃ Spy ์‚ฌ์šฉ

ํ˜„์‹ค์  ๐Ÿ‘
ํ•˜์ง€๋งŒ ์˜ˆ์ธก ์–ด๋ ค์›€ ๐Ÿ‘Ž

3๏ธโƒฃ Stub ๋‹จ์ˆœํ•จ

์ดํ•ด ์‰ฌ์›€ ๐Ÿ‘
ํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธ ๋ฒ”์œ„ ์ œํ•œ ๐Ÿ‘Ž

๐Ÿง  ์ •๋ฆฌ

Mock / Stub / Spy ์ฐจ์ด

Mock โ†’ ์™„์ „ ๊ฐ€์งœ
Stub โ†’ ๊ฒฐ๊ณผ ์ •์˜
Spy โ†’ ์ผ๋ถ€๋งŒ ๊ฐ€์งœ

ํ•ต์‹ฌ ๐Ÿ‘‰ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒ

profile
์ฒœ๋ฐฉ์ง€์ถ•์–ด๋ฆฌ๋‘ฅ์ ˆ๋น™๊ธ€๋น™๊ธ€๋Œ์•„๊ฐ€๋Š”๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€