ํ ์คํธ๊ฐ ๋๋ฌด ๋ฌด๊ฒ๊ณ ๊ท์ฐฎ์์ ๐ฎ๐จ
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ์ธ๋ถ ํ๊ฒฝ๊ณผ ํจ๊ป ๋์ํ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ํ ์คํธํ๊ธฐ๋ ์ฐธ ์ด๋ ค์ด ์ผ์ด๋ค.
๋น์ฆ๋์ค ๋ก์ง์ด ์๋ ์๋น์ค์์ ๋ก์ง์ ๋ง๊ฒ ์ ๋๋ก ๋์ํ๋์ง๋ฅผ ํ ์คํธํ๊ณ ์ถ์๋ฐ,
ํ ์คํธ๋ฅผ ํ๊ธฐ ์ํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ๋ถํฐ ํ์ํ ๋ฐ์ดํฐ๋ค์ ๋ฃ์ด๋๋ ๋ฑ๋ฑ ๊ธฐํ ์ค์ ๋ค์ ํ๋๋ฐ ๋ ๋ง์ ์๊ฐ์ ๋ค์ฌ์ผํ๋ค.
๋ ํ ์คํธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๊ด์ฌํ๋ฉด์ ๋ถํฐ๋, ํ ์คํธ ํ์ ๋ฐ์ดํฐ๋ฅผ ์ง์์ฃผ๋ ๋ฑ ํ์ฒ๋ฆฌ๋ฅผ ์ ๋๋ก ํ์ง ์์์ ์ ์ฒด ํ ์คํธ๋ฅผ ๋๋ ธ์ ๋ ์์์น ๋ชปํ๊ฒ ํ ์คํธ์ ์คํจํ๋ ๊ฒฝํ์ ๋ง์ด ํด๋ดค์ ๊ฒ์ด๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํ ์คํธ ๋๋ธ(Test Double)์ด๋ผ๋ ๊ฐ๋ ์ด ๋์๋ค.
xUnit Test Patterns์ ์ ์์ธ ์ ๋ผ๋ ๋ฉ์ค์๋ก์ค(Gerard Meszaros)๊ฐ ๋ง๋ ์ฉ์ด๋ก ํ ์คํธ๋ฅผ ์งํํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ ์ด๋ฅผ ๋์ ํด ํ ์คํธ๋ฅผ ์งํํ ์ ์๋๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฐ์ฒด๋ฅผ ๋งํ๋ค.
ํ ์คํธ ๋๋ธ์ ์ข ๋ฅ๋ ์ฌ๋ฌ๊ฐ์ง์ด์ง๋ง, ๋ด๊ฐ ์ฌ์ฉํด๋ณธ Mock์ ๋ํด์ ์ ๋ฆฌํด๋ณด๋ ค๊ณ ํ๋ค.
Mock์ด ๋ญ์ผ?
Mock์ "ํ ์คํธ๋ฅผ ์ํํ ๋ชจ๋๊ณผ ์ฐ๊ฒฐ๋๋ ์ธ๋ถ์ ๋ค๋ฅธ ์๋น์ค๋ ๋ชจ๋๋ค์ ์ค์ ์ฌ์ฉํ๋ ๋ชจ๋์ ์ฌ์ฉํ์ง ์๊ณ ์ค์ ์ ๋ชจ๋์ "ํ๋ด"๋ด๋ "๊ฐ์ง" ๋ชจ๋์ ์์ฑํ์ฌ ํ ์คํธ์ ํจ์ฉ์ฑ์ ๋์ด๋๋ฐ ์ฌ์ฉํ๋ ๊ฐ์ฒด" ๋ผ๊ณ ํ๋ค.
๊ทธ๋ฅ ๊ฐ๋จํ๊ฒ ํ ์คํธ ์ ์์ง ๊ตฌํ์ด ๋์ง ์์๊ฑฐ๋ ํ ์คํธ ํ๊ธฐ ์ด๋ ต๊ณ , ๋ถํ์ํ ๊ฐ์ฒด๋ฅผ ๊ฐ์ง ๊ฐ์ฒด(Mock)๋ก ๋ฐ๊ฟ์น๊ธฐ ํ๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
์๋ฅผ ๋ค์ด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐ๋๋ ๊ฐ์ฒด๋ฅผ ๊ฐ์ง ๊ฐ์ฒด๋ก ๋ฐ๊พธ๊ณ , ๊ทธ ๊ฐ์ฒด์ ํ๋์ ์ํ๋๋๋ก ์ง์ ํ์ฌ ํ ์คํธ๋ฅผ ํธ๋ฆฌํ๊ฒ ํ ์ ์๋ค.
๐ก Mock์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
Mock์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์๋ 3๊ฐ์ง๊ฐ ์๋ค.
- Mockito.mock()
- @Mock
- @MockBean
์์ ๋ฐฉ๋ฒ์ ์ฐจ์ด์ ์ ๋ชจ๋ฅด๊ณ ์ฌ์ฉํ์๋ค. ๊ทธ๋ฐ๋ฐ ์ธ์ ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผ ํ ์ง๋ ์๊ณ ์จ์ผ ํ์ง ์์๊น ๐คจ
ํ ์คํธ์ ํ๋์ฉ ์ ์ฉํด๋ณด๋ฉด์ ์ฐจ์ด์ ์ ์์๋ณด์.
โ ํ ์คํธํ ์๋น์ค ๋ก์ง
@Service
public class GameService {
private final GameRepository gameRepository;
public GameService(GameRepository gameRepository) {
this.gameRepository = gameRepository;
}
public List<GameResult> findAllGameResult() {
List<GameResult> gameResultList = gameRepository.findAllGame();
if (gameResultList.size() == 0) {
throw new EmptyGameException();
}
return gameResultList;
}
}
์์ ๊ฐ์ด GameService๊ฐ ์๊ณ , GameService๋ GameRepository์ ์์กดํ๋ค.
findAllGameResult()๋ฅผ ๋ณด๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํ์ฌ ์ ์ฅ๋ ๊ฒ์ ๋ชฉ๋ก๋ค์ ๊ฐ์ ธ์์ ๋ฐํํด์ฃผ๋๋ฐ, ์ด๋ ๊ฒ์์ด ์๋ฌด๊ฒ๋ ์๋ ๊ฒฝ์ฐ EmtpyGameException ์์ธ๋ฅผ ๋์ง๋ค.
๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉด ์์ธ๋ฅผ ๋์ง๋ค๋ ์ค์ ์ ๋น์ฆ๋์ค ๋ก์ง์ ์ด๋ป๊ฒ ํ ์คํธํ ์ง๋ฅผ ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ๊ทน๋จ์ ์ธ ์์๋ฅผ ๋ค์๋ค.
๊ทธ๋ฆฌ๊ณ ์ค์ํ์ง ์์ง๋ง, ๋ฐํ๊ฐ์ผ๋ก ์ฌ์ฉ๋๋ GameResult๋ ์๋์ ๊ฐ๋ค.
public class GameResult {
private final int gameId;
private final boolean win;
private GameResult(int gameId, boolean win) {
this.gameId = gameId;
this.win = win;
}
public static GameResult of(int gameId, boolean win) {
return new GameResult(gameId, win);
}
// getter ...
}
โ ํ ์คํธ ํด์ผ ํ ๋ถ๋ถ
- gameRepostitory.findAllGame()์ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ List<GameResult>๋ฅผ ์ ๋๋ก ๋ฐํํ๋์ง
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ๋ฐ์ง ๋ชปํ๋ฉด(๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉด) EmptyGameException์ ๋์ง๋์ง
1. Mockito.mock()
Mockito ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค.
public class GameServiceTest {
private GameService gameService;
@Test
public void findAllGameResultTest() {
// Mockito.mock()์ ์ฌ์ฉํ์ฌ Mock ๊ฐ์ฒด ์์ฑ
GameRepository gameRepository = Mockito.mock(GameRepository.class);
gameService = new GameService(gameRepository);
}
}
GameService์ GameRepository๋ฅผ ์ฃผ์ ํด์ฃผ์ด์ผ ํ๋๋ฐ, ์ด๋ Mock ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ฃผ์ ํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ ๋์ ๋ค์๊ณผ ๊ฐ์ด GameRepository์ ๋ฉ์๋์ ํ๋์ ์ ํ ์ ์๋ค.
public class GameServiceTest {
private GameService gameService;
private GameRepository gameRepository;
@BeforeEach
void setUp() {
gameRepository = Mockito.mock(GameRepository.class);
gameService = new GameService(gameRepository);
}
@Test
@DisplayName("๊ฒ์์ด ์กด์ฌํ ๊ฒฝ์ฐ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.")
public void findAllGameResultTest() {
List<GameResult> gameResultList = List.of(
GameResult.of(1, true)
);
// gameRepository์ findAllGame() ๋ฉ์๋ ํธ์ถ ์ gameResultList๋ฅผ ๋ฐํํ๋๋ก ์ง์
Mockito.when(gameRepository.findAllGame())
.thenReturn(gameResultList);
List<GameResult> allGameResult = gameService.findAllGameResult();
assertAll(
() -> assertThat(allGameResult.get(0).getGameId()).isEqualTo(1),
() -> assertThat(allGameResult.get(0).isWin()).isTrue()
);
}
@Test
@DisplayName("๊ฒ์์ด ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ ์์ธ๋ฅผ ๋์ง๋ค.")
public void findAllGameResultTest_Exception() {
List<GameResult> gameResultList = new ArrayList<>();
Mockito.when(gameRepository.findAllGame())
.thenReturn(gameResultList);
assertThatThrownBy(() -> gameService.findAllGameResult())
.isInstanceOf(EmptyGameException.class);
}
}
๊ทธ๋ฆฌ๊ณ ํ ์คํธ๊ฐ ์ฑ๊ณตํ ๊ฒ์ ๋ณผ ์ ์๋ค.
2. @Mock
Mockito๋ ์ด๋ ธํ ์ด์ ๋ ์ง์ํ๋ค.
Mockito.mock() ๋์ ํ๋์ @Mock ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ฉด ๋๋ค.
์ฌ๊ธฐ์ ์ฃผ์ํ ์ ์ ํด๋์ค์ @ExtendWith(MockitoExtension.class)๋ฅผ ๋ถ์ฌ์ฃผ์ด์ผ ํ๋ค.
๊ทธ๋์ผ @Mock ์ด๋ ธํ ์ด์ ์ด ๋ถ์ ๊ฐ์ฒด๋ฅผ ์ฐพ์ Mock ๊ฐ์ฒด๋ฅผ ์์ฑํ ์ ์๋ค.
@ExtendWith(MockitoExtension.class)
public class GameServiceTest {
private GameService gameService;
@Mock
private GameRepository gameRepository;
@Test
public void findAllGameResultTest() {
gameService = new GameService(gameRepository);
}
}
3. @MockBean
@MockBean์ด๋ผ๋ ์ด๋ ธํ ์ด์ ๋ ์๋ค.
์ผํ๋ณด๋ฉด @Mock ๊ณผ ๋น์ทํด๋ณด์ธ๋ค.
๊ณผ์ฐ ์ฐจ์ด์ ์ด ๋ฌด์์ผ๊น?
์ผ๋จ MockBean์ ๊ฒฝ๋ก๊ฐ Mock๊ณผ ๋ค๋ฅด๋ค.
import org.springframework.boot.test.mock.mockito.MockBean;
์์ ๊ฒฝ๋ก๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด, @MockBean์ @Mock๊ณผ ๊ฐ์ ์ญํ ์ ํ๋๋ฐ ์คํ๋ง ์์ญ์์ ๋์ํ๋ค.
์คํ๋ง์ ์์กด ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก ์ฃผ์ ํ๋๋ฐ,
์ด๋ @MockBean ์ด๋ ธํ ์ด์ ์ด ๋ถ์ ๊ฐ์ฒด๋ฅผ Mock ๊ฐ์ฒด๋ก ์คํ๋ง ์ปจํ ์ด๋์ ๋ฑ๋กํ๋ค.
๊ทธ๋์ ํ์ฌ GameServiceTest๋ ์คํ๋ง ํ๊ฒฝ์ ํ ์คํธ๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด @MockBean์ ๋ถ์ฌ์ค๋ค๊ณ ํด๋ ์ ๋๋ก ๋์ํ์ง ์๋๋ค.
Mock์ผ๋ก ๋ง๋ค์ด์ง์ง๋ ์์ ๋ฟ๋๋ฌ, ์๋์ผ๋ก ์์กด ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํด์ฃผ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ ๋น์ฐํ NullPointerException์ด ํฐ์ง๋ค.
์๋ ์ฝ๋์ ๊ฐ์ด @SpringBootTest๋ฅผ ๋ถ์ฌ ์คํํ๋ ํ ์คํธ๋ ์ฑ๊ณตํ๋ค.
@SpringBootTest
public class GameServiceTest {
@MockBean
private GameRepository gameRepository;
@Autowired
private GameService gameService;
// Test ์๋ต
}
๊ฒฐ๋ก ์ @MockBean์ ์คํ๋ง ํ๊ฒฝ์ ํ ์คํธ์์ ์ฌ์ฉํด์ผํ๋ค!
4. ๊ฒฐ๋ก
์ด๋ ๊ฒ Mock์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์์๋ณด์๋ค.
Mock์ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ญ๊น์ง ๊ฐ์ง ์๊ณ ๋น์ฆ๋์ค ๋ก์ง์ ์ด์ ์ ๋ง์ถฐ ํ ์คํธํ ์ ์๊ฒ ๋์๋ค.
์ด์ ๋ ํ ์คํธ๋ฅผ ์์ฑํ ๋ ์คํธ๋ ์ค๋ฅผ ๋ ๋ฐ์ ์ ์๊ฒ ๋ค ๐
Mockito ํ๋ ์์ํฌ์ ๋ฉ์๋ ์ฌ์ฉ๋ฐฉ๋ฒ์ ๋ํด์๋ ์ค๋ช ํ์ง ์์๋๋ฐ, ๋๋ ์์ง ์ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค..
์ฐจ์ฐจ ์ฌ์ฉํด๋ณด๋ฉด์ ๊ณต๋ถํ ์์ ์ด๋ค.
[์ฐธ๊ณ ์๋ฃ]
Mockito @Mock @MockBean @Spy @SpyBean ์ฐจ์ด์
์์ ์ฝ๋ https://github.com/cobiyu/MockitoSample Test Double์ด ์ ํ์ํ ์ง๋ถํฐ ์์ํ๋ ๊ธฐ๋ณธ์ ์ธ ํ ์คํธ ์ฝ๋๋ถํฐ ํ ๋จ๊ณ์ฉ ๋ฐ์ ์์ผ๋๊ฐ๋ฉฐ Mockito์ ์ด๋ ธํ ์ด์ ๋ค์ ์ ํํ ์ฐ์์์ ๋ํด ์ดํด๋ณด๊ฒ ์ต
cobbybb.tistory.com
https://stackoverflow.com/questions/44200720/difference-between-mock-mockbean-and-mockito-mock
Difference between @Mock, @MockBean and Mockito.mock()
When creating tests and mocking dependencies, what is the difference between these three approaches? @MockBean: @MockBean MyService myservice; @Mock: @Mock MyService myservice; Mockito.mock()
stackoverflow.com
'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Boot] Graceful shutdown์ผ๋ก ์์ ํ๊ฒ ์ข ๋ฃํ๊ธฐ (0) | 2024.08.30 |
---|---|
[JPA] ์ฐ๊ด๊ด๊ณ ๋งคํ ์ ๋ฆฌ (0) | 2023.07.13 |
Dao์ Repository์ ๋ํด์... (0) | 2023.05.29 |
[Spring] Interceptor๋ ์ด๋ป๊ฒ ๋์ํ ๊น? - DispatcherServlet ๋ด๋ถ ์ฝ๋ ์ดํด๋ณด๊ธฐ (0) | 2023.05.08 |