일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- union find
- SQL
- JOIN
- 프림
- AWS
- 그래프 탐색
- 크루스칼
- django
- 자바
- db
- 구현
- Java
- GROUPBY
- 코딩테스트
- 알고리즘
- MST
- OrderBy
- 백준
- 다익스트라
- SQL코딩테스트
- 배포
- BFS
- 최단경로
- 피로그래밍
- EC2
- 누적합
- Baekjoon
- Database
- Pirogramming
- 프로그래머스
- Today
- Total
NullNull
Mock? Spy? 본문
이 둘은 모두 Mockito 라이브러리를 사용해서 활용할 수 있습니다. 간단하게 어노테이션만 붙여주면서 객체의 동작을 제어할 수 있는데요. 이 두 개념은 비슷하지만 중요한 차이가 존재합니다. 이 글에서는 Mock 과 Spy 의 차이를 비교하고, 어떤 경우에 사용해야 하는지 정리해보겠습니다.
1. Mock vs Spy: 개념적 차이
우선 둘은 주로 단위 테스트 에서 사용됩니다.
Mock
@Mock은 가짜 객체를 생성하는 것입니다.
이 객체는 모든 메서드를 모킹하여 실제 동작 없이 지정된 반환 값을 제공합니다. 즉 특정 클래스 안에 있는 모든 메서드는 껍데기만 존재할 뿐 아무 로직이 없다고 가정합니다. Mockito.when()으로 mocking 하지 않는다면, 아무 동작을 수행하지 않게 됩니다. 물론 mocking 한 메서드에 대해서는 해당 메서드가 불릴 때, 실제 로직 대신 mocking 하도록 작성한 로직을 수행합니다.
Spy
@Spy는 실제 객체를 감싸는 방식입니다.
Mock 과 비슷하게 모든 메서드를 mocking 할 수 있지만, 아무런 로직도 mocking 하지 않는다면 모든 메서드가 실제 객체의 로직대로 실행됩니다. 물론 변경하고 싶은 일부 메서드를 mocking 한다면, 그 메서드는 mocking 한 로직대로 동작하게 됩니다. @Spy는 단위 테스트에서 실제 로직의 일부만 변경하고 싶은 경우에 유용합니다.
2. Mock과 Spy의 동작 방식 비교
Mock: 모든 메서드 모킹
@Mock은 실제 로직을 실행하지 않고 우리가 설정한 반환 값만 반환합니다. 예를 들어, 아래 예시처럼 PlayerRepository의 createPlayer와 getPlayer 메서드를 모두 mocking 하면, 실제 데이터베이스와의 상호작용 없이 테스트를 진행할 수 있습니다. 이렇게 하면 외부 의존성을 격리하고, 순수한 단위 테스트가 가능합니다.
Spy: 일부 메서드만 모킹
@Spy는 메서드는 실제 객체의 로직을 그대로 실행하면서 일부 메서드만 mocking 할 수 있습니다. 아래 예시는, createPlayer는 mocking 하고, getPlayer는 실제 구현을 통해 데이터를 가져오는 방식입니다. 이렇게 하면 객체의 일부분은 실제 동작을, 나머지는 모킹하여 더 세밀한 제어가 가능합니다.
3. Mock과 Spy의 차이점 비교
특성 | @Mock (모든 메서드 모킹) | @Spy (일부 메서드만 모킹) |
동작 방식 | 모든 메서드가 모킹되며, 실제 로직이 실행되지 않음 | 일부 메서드만 모킹되고, 나머지는 실제 로직이 실행됨 |
테스트 방식 | 두 메서드 모두 모킹하여 반환값만 설정 | createPlayer는 모킹하고, getPlayer는 실제 실행 |
검증 방식 | 두 메서드 모두 모킹된 상태에서 검증 | 실제 동작을 실행한 getPlayer 메서드는 실제 로직대로 동작 |
4. 코드 예시로 보는 Mock과 Spy의 차이
아래 2개의 Class 가 존재하고, PlayerService 에 대한 테스트를 작성한다고 가정하겠습니다.
PlayerServiceImpl Class
package com.example.soccerteam.player;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class PlayerServiceImpl implements PlayerService {
private final PlayerRepository playerRepository;
public PlayerServiceImpl(PlayerRepository playerRepository) {
this.playerRepository = playerRepository;
}
@Override
public Player createPlayer(Player player) {
return playerRepository.createPlayer(player);
}
@Override
public Player getPlayer(String name) {
return playerRepository.getPlayer(name);
}
}
PlayerRepositoryImpl Class
package com.example.soccerteam.player;
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
@Repository
public class PlayerRepositoryImpl implements PlayerRepository {
private final Map<String, Player> playerStorage = new HashMap<>();
public Player createPlayer(Player player) {
playerStorage.put(player.getName(), player);
return player;
}
public Player getPlayer(String name) {
return playerStorage.get(name);
}
}
Mock 사용 예시: 두 개의 메서드를 모두 모킹
class PlayerServiceMockTest {
@Mock
private PlayerRepository playerRepository;
@InjectMocks
private PlayerService playerService;
@Test
void testCreateAndGetPlayerWithMock() {
//0.
Player player1 = new Player("HAALAND", "1999.10.18", 9, "Norway", true, 197, 77);
Player player2 = new Player("DE BRYUNE", "1993.10.18", 17, "Belgium", true, 183, 70);
//1.
when(playerRepository.createPlayer(player1)).thenReturn(player1);
when(playerRepository.getPlayer("HAALAND")).thenReturn(player1);
when(playerRepository.createPlayer(player2)).thenReturn(player2);
when(playerRepository.getPlayer("DE BRYUNE")).thenReturn(player2);
//2.
Player result1 = playerService.createPlayer(player1);
Player result2 = playerService.createPlayer(player2);
//3.
Player result1Get = playerService.getPlayer("HAALAND");
Player result2Get = playerService.getPlayer("DE BRYUNE");
//4.
Assertions.assertEquals(player1, result1);
Assertions.assertEquals(player2, result2);
Assertions.assertEquals(player1, result1Get);
Assertions.assertEquals(player2, result2Get);
//5.
verify(playerRepository).createPlayer(player1);
verify(playerRepository).createPlayer(player2);
verify(playerRepository).getPlayer("HAALAND");
verify(playerRepository).getPlayer("DE BRYUNE");
}
}
- 동작:
- createPlayer, getPlayer 을 모두 mocking 합니다.
- playerService.createPlayer 가 불리면, 내부에 있는 playerRepository.createPlayer 에서 실제 객체를 생성하지 않고, mocking 로직에 따라 0번에서 생성한 player1 또는 player2 를 리턴합니다.
- getPlayer 가 불리면, 마찬가지로 mokcing 로직에 따라 입력값에 맞는 0번에서 생성한 인스턴스를 리턴합니다. 결국 4, 5 번 테스트는 성공하게 됩니다.
- 사용 시기: 외부 시스템과의 의존성을 완전히 격리하고, 두 메서드의 동작을 독립적으로 테스트하고 싶을 때 유용합니다.
Spy 사용 예시: 두 개의 메서드 중 하나만 모킹
class PlayerServiceSpyTest {
@Spy
private PlayerRepository playerRepository = new PlayerRepositoryImpl();
@InjectMocks
private PlayerService playerService;
@Test
void testCreateAndGetPlayerWithSpy() {
//0.
Player player1 = new Player("HAALAND", "1999.10.18", 9, "Norway", true, 197, 77);
Player player2 = new Player("DE BRYUNE", "1993.10.18", 17, "Belgium", true, 183, 70);
//1.
doReturn(player1).when(playerRepository).createPlayer(player1);
doReturn(player2).when(playerRepository).createPlayer(player2);
//2.
Player result1 = playerService.createPlayer(player1);
Player result2 = playerService.createPlayer(player2);
//3.
Player result1Get = playerService.getPlayer("HAALAND");
Player result2Get = playerService.getPlayer("DE BRYUNE");
//4.
Assertions.assertEquals(player1, result1);
Assertions.assertEquals(player2, result2);
//5.
Assertions.assertNotNull(result1Get);
Assertions.assertNotNull(result2Get);
}
}
- 동작:
- createPlayer 을 mocking 합니다. 0번에서 mocking 된 인스턴스가 리턴됩니다.
- playerService.createPlayer 가 불리면, 내부에 있는 playerRepository.createPlayer 에서 실제 객체를 생성하지 않고, mocking 로직에 따라 0번에서 생성한 player1 또는 player2 를 리턴합니다.
- getPlayer은 mocking 되지 않았으므로, 이 테스트에서는 2가지 경우로 동작합니다.
- 만약, Repository 의 playerStorage 에 선수들이 미리 저장되어 있다면, 실제 저장된 객체를 이름에 맞게 가져옵니다. 그리하여 4, 5번 테스트는 성공하게 됩니다.
- 미리 선수들이 저장되지 않았다면, 실제 저장된 객체가 없으므로 선수들을 가져올 수 없습니다. 그리하여 4, 5번 테스트는 실패하게 됩니다.
- 사용 시기: 실제 객체의 동작을 일부만 사용하고, 다른 동작은 모킹하려 할 때 유용합니다. 예를 들어, createPlayer는 테스트가 필요하지만 getPlayer는 실제 동작을 통해 확인하고 싶을 때 적합합니다.
5. 결론
만약 의존성을 완전히 제거하고, 순수 클래스의 기능을 검증하는 단위 테스트를 작성하고 싶다면, Mock 을 사용하여 내부에 있는 모든 외부 의존성을 mocking 하여 테스트를 수행할 수 있습니다.
반면에, 특정 클래스의 기능을 검증할 때, 너무 많은 메서드를 mocking 해야 하거나, 이미 잘 동작되는 것을 확인하여 mocking 이 필요없는 경우가 존재한다면, Spy 를 사용하여 테스트를 수행할 수 있습니다.
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
ObjectMother 패턴 적용하기 (0) | 2024.04.04 |
---|---|
간단한 Unit Test 작성하기 (0) | 2024.04.04 |
객체지향 언어의 특징과 설계 원칙 (0) | 2022.09.29 |
예외 처리 (0) | 2022.08.14 |
Abstract class vs Interface (0) | 2022.08.14 |