해당 글은 jsp/servlet 개발 환경에 해결했던 내용입니다.
테스트 코드 작성을 위한 의존 관계 주입(DI)을 고려해야할 상황
BoardService는 게시글 번호에 해당하는 게시글을 가져오기 위해서 BoardDAO, CommentDAO, FileDAO에게 데이터 베이스 접근 로직을 위임하고 있습니다.
작성된 코드는 아래와 같습니다.
public BoardDTO findBoard(Long boardIdx) {
BoardDTO boardDTO = boardDAO.findById(boardIdx);
if( boardDTO == null ) {
throw new NoSuchElementException("해당 글을 찾을 수 없습니다.");
}
boardDAO.increaseHitCount(boardIdx)
boardDTO.setComments(commentDAO.findAllByBoardId(boardIdx));
boardDTO.setFiles(fileDAO.findFilesByBoardId(boardIdx));
return boardDTO;
}
BoardService가 작업을 완료하기 위해 BoardDAO, CommentDAO, FileDAO에 의존하고 있으며, 이는 의존관계를 가지고 있다는 의미입니다.
이와 같은 상황에서 테스트 코드를 작성하려고 할때 어떻게 해야할지 고민을 하게 됩니다.
BoardService는 BoardDAO, FileDAO에 대한 의존 관계를 가진다.
public class BoardService {
private static BoardService boardService;
private BoardDAO boardDAO = BoardDAO.getInstance();
private CommentDAO commentDAO = BoardDAO.getInstance();
private FileDAO fileDAO = BoardDAO.getInstance();
private BoardService() {};
public static BoardService getInstance() {
if (boardService == null) {
boardService = new BoardService();
}
return boardSerivce;
}
}
현재 위와 같이 싱글톤 패턴으로 구현된 클래스와 의존관계를 가지는 경우, 다음과 같은 문제가 있습니다.
- 해당 클래스와 강한 의존관계를 가지기 떄문에 테스트하기 어렵습니다.
- 생성자를 private 구현하기 때문에 상속을 할 수 없습니다.
따라서, 싱글톤 패턴을 사용하지 않으면서 인스턴스를 하나만 유지할 수 있는 DI 구조로 변경해 보겠습니다.
public class BoardService {
private BoardDAO boardDAO;
private CommentDAO commentDAO;
private FileDAO fileDAO;
public BoardService(BoardDAO boardDAO, CommentDAO commentDAO, FileDAO fileDAO) {
this.boardDAO = boardDAO;
this.commentDAO = commentDAO;
this.fileDAO = fileDAO;
}
}
Mockito를 활용한 테스트 코드
데이터베이스가 없는 상태에서도 테스트가 가능하도록 지원하는 Mock 테스트 프레임워크를 사용해 보겠습니다.
우선 gradle 의존성을 추가합니다.
// junit 5
implementation 'com.mikemybytes:junit5-formatted-source:0.2.0'
implementation 'com.mikemybytes:junit5-formatted-source-parent:0.2.0'
implementation 'com.mikemybytes:junit5-formatted-source-tests:0.2.0'
// mockito
testImplementation 'org.mockito:mockito-junit-jupiter:3.11.2'
Mockito를 활용해 BoardService의 findBoard() 메서드를 테스트 해보겠습니다.
@ExtendWith(MockitoExtension.class)
public class ServiceTest {
@Mock
private BoardDAO boardDAO;
@Mock
private CommentDAO commentDAO;
@Mock
private FileDAO fileDAO;
@Mock
private CategoryDAO categoryDAO;
private BoardService boardService;
@BeforeEach
void sertup() {
boardService = new BoardService(boardDAO, commentDAO, categoryDAO, fileDAO);
}
@Nested
@DisplayName("게시글이 가져올때")
class getBoard {
@Test
@DisplayName("조회수가 증가되지 안았다면 예외가 발생한다")
void find_by_id_exception() {
// given
Long boardIdx = 1L;
when(boardDAO.findById(boardIdx)).thenReturn(null);
// when
assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> {boardService.findBoard(1);})
.withMessageMatching("해당 글을 찾을 수 없습니다.");
}
@Test
@DisplayName("조회수 증가, 게시글 찾기, 모든 댓글 찾기, 모든 파일 찾기의 메서드가 순서대로 호출된다.")
void board_click_triggers_method_calls() {
// given
Long boardIdx = 1L;
BoardDTO boardDTO = new BoardDTO();
when(boardDAO.findById(boardIdx)).thenReturn(boardDTO);
// when
boardService.findBoard(boardIdx);
// then
InOrder inOrder = inOrder(boardDAO, commentDAO, fileDAO);
inOrder.verify(boardDAO).findById(boardIdx);
inOrder.verify(boardDAO).increaseHitCount(boardIdx);
inOrder.verify(commentDAO).findAllByBoardId(boardIdx);
inOrder.verify(fileDAO).findFilesByBoardId(boardIdx);
inOrder.verifyNoMoreInteractions();
}
}
위 코드를 보면 Mockito는 @Mock 어노테이션으로 설정한 클래스의 메서드를 호출했을 때 반환 값을 지정할 수 있습니다.
또한, 클래스의 메서드가 호출되는지 여부를 verify() 메서드를 통해 검증하는 작업 또한 가능합니다.
'Java' 카테고리의 다른 글
| 파일(File) 도메인 객체 설계와 테스트 코드 작성 (0) | 2023.07.11 |
|---|---|
| 게시글(POST) 도메인 객체 설계와 테스트 코드 작성을 통한 리팩토링 (0) | 2023.07.05 |
| 바이너리(binary) 파일을 다운로드하는 프로세스와 버퍼를 사용해야하는 이유 (0) | 2023.05.29 |
| Multi-Thread 환경에서 싱글톤 패턴으로 구현했을 경우 발생할 수 있는 문제 (0) | 2023.05.29 |
| Initialized DataBase / 톰캣(Tomcat) 실행 시 데이터베이스 초기화 (0) | 2023.05.23 |