해당 글은 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 |