[ KOSTA 교육 58일차 ] JUnit | WebMvcTest, SpringBootTest, DataJpaTest, Mockito, Mock, MockBean, InjectMocks | Swagger

junjun·2024년 7월 29일

KOSTA

목록 보기
46/48

JUnit

  • 등장 배경 : 소프트웨어에 대한 개발자의 책임이 높아지는 동시에, 자동화 테스트에 대한 관심이 높아지며 이에 따라 다양한 테스트를 지원하는 프레임워크가 등장했고, 그 중 하나가 JUnit.

  • xUnit의 Java 버전으로, Java 진영에서 단위 테스트를 작성할 수 있도록 다양한 어노테이션과 메소드를 지원한다.

JUnit이 Test를 수행하는 방식

  • JUnit은 @Test가 붙은 메소드를 테스트 메소드로 인식하여 이를 추적한다.

  • 해당 테스트 메서드를 자동화된 방법으로 검증하고, GUI를 통해 결과값을 제시한다.

< JUnit의 메소드 단위 생명주기 >

  1. 테스트 클래스에서 @Test public void 로 정의한 메소드를 모두 찾는다.
  2. 테스트 클래스의 오브젝트를 하나 만든다. ( 리플랙션 )
  3. setUp 단계 : @Before 메소드를 실행한다.
  4. @Test 단계 : @Test 메소드를 호출하고 테스트 결과를 저장해둔다.
  5. tearDown 단계 : @After 메소드를 실행한다.
  6. 나머지 테스트 메소드에 대해 3~5번을 반복한다. ( 순서는 보장되지 않음 )
  7. 모든 테스트의 결과를 종합해서 돌려준다.

setUp 단계

  • 테스트에 필요한 오브젝트 또는 환경을 정의, @Test 단계의 setUp에서 정의한 정보를 전달받아 테스트 결과를 검증한다.

tearDown 단계

  • 정보를 정리하거나, 테스트 검증 후 취할 동작을 정의한다.
  • 다음 검증할 @Test 메소드 또한 이러한 일련의 프로세스를 거친다.

Fixture

  • setUp 단계에서 정의한 정보

  • 즉, 테스트를 수행하는 데 필요한 정보나 오브젝트

  • 테스트에서는 픽스처를 정의하는 시점에 따라 테스트의 결과가 달라지기에, 픽스처의 정의 시점이 매우 중요함.

  • 각 메소드 단계 전에 실행할 것인지 (@BeforeEach ), 테스트 클래스가 실행하는 시점에 실행할 것인지 ( @BeforeAll )에 따라 테스트 결과가 다를 수 있어, 이러한 픽스처를 정의하는 시점을 관리하는 것이 매우 중요하다.

Fixture Method

  • JUnit은 픽스처를 관리할 수 있는 테스트 어노테이션을 제공하고 있음.

  • 이러한 테스트 어노테이션이 붙은 메소드를 픽스처 메소드라 부름
    ( @Before, @After가 붙은 메소드가 이에 속함 )

JUnit이 테스트 메소드를 인식하는 법

  1. 테스트할 메소드에 @Test 어노테이션을 지정함.

  2. 접근제한자는 반드시 public으로 정의함.

  3. 리턴 타입은 void로 테스트 메소드는 반환 값이 없음.

  • 상황에 맞게 테스트를 할 수 있도록 2가지 옵션을 제공함
    ( 시간제한 timeout=long, expected=Class<? extends Throwable> )

JUnit 과 친해지기 (예제)

1. BoardControllerTest - 1

@WebMvcTest(BoardController.class)
public class BoardControllerTest {

	@Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private BoardService boardService;
    
    
    @Test
    @DisplayName("MockMvc를 통한 Board 데이터 가져오기")
    void getBoardTest() throws Exception {
    	
        // given
        Long boardId = 123L;
        BoardEntity boardEntity = new BoardEntity();
        boardEntity.setBseq(boardId);
        boardEntity.setTitle("Test Title");
        boardEntity.setContents("Test Contents");
        boardEntity.setRegid("testuser");
        boardEntity.setRegdate(new Date());
        
        given(boardService.svcBoardDetail(boardId)).willReturn(boardEntity);
        
        // andExpect
        mockMvc.perform(get("/board_detail/" + boardId))
        	.andExpect(status().isOk())
            .andExpect(view().name("lec06_html/board_detail"))
            .andExpect(model().attributeExists("MY_BAORD_VO"))
            .andExpect(model().attribute("MY_BOARD_VO", hasProperty("bseq", is(123L))))
            .andExpect(model().attribute("MY_BOARD_VO", hasProperty("title", is("Test Title"))))
            .andExpect(model().attribute("MY_BOARD_VO", hasProperty("contents", is("Test Contents"))))
            .andExpect(model().attribute("MY_BOARD_VO", hasProperty("regid", is("testuser"))))
            .andDo(print()); // 응답값 print
            
        verify(boardService).svcBoardDetail(123L);
	}
}

1번의 결과

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /board_detail/123
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = com.boot.demo.lec06.BoardController
           Method = com.boot.demo.lec06.BoardController#ctlBoardDetail(Model, Long)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = lec06_html/board_detail
             View = null
        Attribute = MY_BOARD_VO
            value = BoardEntity(bseq=123, search_str=null, title=Test Title, contents=Test Contents, regid=testuser, regdate=Tue Jul 30 09:50:47 KST 2024, files=null)
           errors = []

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Language:"en", Content-Type:"text/html;charset=UTF-8"]
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8">
    <title>게시글 상세</title>
</head>
<body>
<h1>게시글 상세</h1><hr>

...(view 내용들)

Forwarded URL = null
Redirected URL = null
Cookies = []
  1. @WebMvcTest 어노테이션은 무엇일까
  • 스프링 부트에서 제공하는 어노테이션 중 하나로, 웹 어플리케이션의 MVC 컨트롤러(Controller)를 테스트하기 위해 사용됩.

  • 특정 컨트롤러를 대상으로 하는 단위 테스트를 작성할 때 사용한다.

  • 웹 레이어에서 발생하는 요청과 응답을 테스트할 수 있음.

  • 컨트롤러 단위 테스트를 할 때는, 무엇을 가짜로 띄울지 결정할 수 있어야하는 것이 중요.

  1. MockMvc 란 무엇일까
  • 테스트용 MVC 환경을 만들어, 요청 및 전송 & 응답기능을 제공해주는 유틸리티 클래스

  • 실제 서버로 구현한 어플리케이션을 올리지 않고, 테스트용으로 시뮬레이션하여 MVC가 되도록 도와주는 클래스

1-1. MockMvc 작동 순서

  • 1) MockMvc 생성 ( @Autowired )
  • 2) MockMvc에게 요청에 대한 정보를 입력
  • 3) 요청에 대한 응답값을 Expect를 이용하여 테스트
  • 4) Expect가 모두 통과하면 테스트 통과
  • 5) Expect가 1개라도 실패하면 테스트 실패

이 블로그가 잘 정리되어 있는 게 보여 참조
https://velog.io/@jkijki12/Spring-MockMvc

  1. @MockBean이란 무엇일까
  • SprintBoot 1.4에서 추가된 테스트 어노테이션

  • 테스트 더블?

    • 목적에 따라 비슷한듯 하면서도, 다른 객체를 사용하는 모든 행위
    • Stub, Dummy, Mock, Spy...

  • 테스트 범위를 대상코드 자체의 동작과 협력객체와의 연계동작으로만 제한 가능

  • Mock = 껍데기만 있는 객체

  • Mock Bean = 기존에 사용되던 Bean의 껍데기만 가져오고, 내부의 구현 부분은 모두 사용자에게 위임한 형태

    • 해당 Bean의 어떤 메소드에 어떤 값이 입력되면, 어떤 값이 리턴되어야 한다는 내용 모두 개발자 필요에 의해 조작이 가능
    • 기존에 사용되던 스프링 Bean이 아닌 Mock Bean을 주입
  1. given 이란 무엇일까
  • import static org.mockito.BDDMockito.given;

  • 해당 Mock Bean이 어떤 행동을 취하면 어떤 결과를 반환한다를 선언하는 부분

  • given(httpSession.getAttribute("loginUser")).willReturn(customer)

    • httpSession.getAttribute()를 호출할 때, 파라미터 값이 loginUser 라면,
    • willReturn(customer) : customer를 리턴하라는 의미
  1. verify 란 무엇일까.
  • import static org.mockito.Mockito.verify;

  • 스터빙한 메소드가 제대로 실행이 되었는지 확인해보는 메소드

  • 스터빙한 메소드가 실행되었는지, n번 실행되었는지, 실행 시간이 초과되지 않았는지 등을 다양하게 검증해볼 수 있음.

  • verify(T mock, VerificationMode mode)

  • VerificationMode는 검증할 값을 정의하는 메소드


@ExtendWith(SpringExtension.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class BoardRepositoryTest{

	@Autowired
    private BoardRepository boardRepository;
    
    @Autowired
    private ReplyRepository replyRepository;
   	
    @Test
    public void testListBoards() {
    	BoardEntity board1 = new BoardEntity();
        board1.setTitle("Test Title 1");
        board1.setContents("Test Contents 1");
        board1.setRegid("testuser1");
        board1.setRegdate(new Date());

        BoardEntity board2 = new BoardEntity();
        board2.setTitle("Test Title 2");
        board2.setContents("Test Contents 2");
        board2.setRegid("testuser2");
        board2.setRegdate(new Date());
        
        boardRepository.save(board1);
        boardRepository.save(board2);
        
        List<BoardEntity> boards = boardRepository.findAll(Sort.by(Sort.Direction.DESC, "regdate"));
        assertThat(boards).hasSizeGreaterThanOrEqualTo(2);
    }

	@Test
    public void testInsertBoard() {
    	BoardEntity board = new BoardEntity();
        board.setTitle("Test Title");
        board.setContents("Test Contents");
        board.setRegid("testuser");
        board.setRegdate(new Date());

        BoardEntity savedBoard = boardRepository.save(board);
        
        assertThat(savedBoard.getBseq()).isNotNull();
    }
    
    
    @Test
    public void testDetailBoard() {
    	BoardEntity board = new BoardEntity();
        board.setTitle("Test Title");
        board.setContents("Test Contents");
        board.setRegid("testuser");
        board.setRegdate(new Date());

        BoardEntity savedBoard = boardRepository.save(board);

        Optional<BoardEntity> foundBoard = boardRepository.findById(savedBoard.getBseq());
        
        assertThat(foundBoard).isPresent();
        assertThat(foundBoard.get().getTitle()).isEqualTo("Test Title");
    }
    
    @Test
    public void testUpdateBoard() {
    	
    }

}

@DataJpaTest ( @ExtendWith )

0개의 댓글