[54일차] Mockito, TDD

유태형·2022년 7월 14일
0

코드스테이츠

목록 보기
54/77

오늘의 목표

  1. Mockito
  2. TDD



내용

Mockito

Mock

Mock은 개발에서 뿐만아니라 실 생활에서도 많이 사용됩니다. 몇번은 들어봤을 단어 Mock-up(목업)이 대표적인 예 입니다. 전시용 휴대폰을 목업 휴대폰이라고도 합니다. 실제 처럼 생겼지만 실제의 기능을 모두 가지지 않은 테스트용, 전시용으로 많이 사용됩니다.

스프링의 테스트 세계에서 Mock가짜 객체를 의미합니다.
단위 테스트나 슬라이스 테스트 등에 Mock객체를 사용하는것을 Mocking이라고 합니다.

예를들어 컨트롤러를 테스트하고 싶다면 컨트롤러에서 서비스, 서비스에서 호출하는 레포지토리, 데이터베이스등을 모두 호출해야 합니다.

하지만 슬라이스 테스트는 해당 계층만 테스트 해야 하므로 실제로 다른 계층으로 넘어가지 않고 다른 계층으로 넘어 간 것 처럼 구현해주는 Mock객체를 사용하므로써 해당 계층 영역에 대한 테스트에 집중할 수 있도록 해줍니다.

컨트롤러에서 실제 서비스를 호출하지않고 Mock 프레임워크에서 만든 Mock서비스를 호출하는 방식으로 계층간 연결을 끊습니다.



Mockito

Mockito는 Mock객체를 생성하고 진짜처럼 동작하게 만들어주는 역할을 수행하는 Mocking 라이브러리 입니다.

@SpringBootTest
@AutoConfigureMockMvc
class 테스트{
	@Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private Gson gson;
    
    @MockBean
    private 서비스 서비스;
    
    @Autowired
    private Mapper mapper;
    
    @Test
    void postMemberTest() throws Exception{
    	//given
        Dto dto = new Dto(....);
        엔티티 엔티티 = mapper.dtoTo엔티티(dto);
        
        given(서비스.메서드(Mockito.any(엔티티.class)))
        	.willReturn(엔티티);
            
        String content = gson.toJson(dto);
        
        //when
        ResultActions actions =
        	mockMvc.perform(
            	post("URI")
                	.accept(MediaType.APPLICATION_JSON)
                    .contentType(MediaType.APPLIACTOIN_JSON)
                    .content(content)
            );
            
        //then
        MvcResult result = actions
        						.andExpect(status().isCreated())
                                .andExpectc(jsonPath("$....").value(dto.get...()))
								.andExpectc(jsonPath("$....").value(dto.get...()))
								.andExpectc(jsonPath("$....").value(dto.get...()))
 								.andReturn();
                                
     	System.out.println(result.getResponse().getContentAsString());
    }
}
  • @MockBean : Application Context에 등록되어 있는 Bean에 대한 Mock 객체를 생성하고 주입하는 역할을 수행합니다.
  • given(서비스.메서드(Mockito.any(엔티티.class))) Mock객체가 특정 값을 리턴하는 동작을 지정합니다. Mock 객체인 서비스의 메서드를 정의합니다. Mockito.any(엔티티.class))는 변수타입 중 하나로 매개변수의 타입을 정의합니다.
  • .willReturn(stub객체) : Mock객체는 실제 기능을 구현하는것이 아니므로 실제처럼 행동하기 위해 미리 정의된 값을 반환합니다. .willReturn()메서드로 반환값을 미리 지정할 수 있습니다. 이처럼 미리 정해진 반환 객체를 stub객체라고 부릅니다.

컨트롤러 클래스의 핸들러 메서드 뿐만 아니라 서비스 클래스의 메서드에서도 Mockito를 활용하여 다른 계층과 독립적으로 슬라이스 테스트를 진행 할 수 있습니다.

이 경우 레포지토리를 호출하는 부분이 Mocking될 것입니다.

@ExtendWith(MockitoExtension.class)
public class 테스트{
	@Mock
    private 레포지토리 레포지토리;
    
    @InjectMock
    private 서비스 서비스;
    
    @Test
   	public void 테스트(){
    	//given
        엔티티 엔티티 = new 엔티티(...);
        
        given(레포지토리.findBy필드(엔티티.get필드()))
        	.willReturn(Optional.of(엔티티));
            
        assertThrows(예외.class, () -> 서비스.메서드(엔티티));
    }
}
  • @ExtendWith(MockitoExtension.class) : Spring을 사용하지 않고 Junit에서 Mockito 기능을 사용합니다.
  • @Mock 필드에 해당하는 Mock객체를 생성합니다.
  • @InjectMock : 필드에 해당하는 Mock객체를 만들고 DI로 Mock객체를 주입합니다.
  • given(레포지토리.findBy필드(엔티티.get필드())).willReturn(Optional.of(엔티티)); : 서비스에서 레포지토리의 메서드를 호출하는 부분을 Mocking합니다. 즉 실제 페포지토리가 아닌 Mock레포지토리의 메서드를 정의합니다. willReturn()메서드는 반환할 stub객체를 정의합니다. stub객체는 개발자가 사전에 정해놓은 고정된 데이터입니다.



TDD

TDD는 기존의 전통적인 개발 방식과 사뭇 다릅니다.

세부 동작을 코드로 구현하면서 해당 메서드의 기능 구현이 끝났다면 기능이 잘 동작하는지 테스트 하는 것 과는 달리 먼저 테스트를 하고 테스트과 통과되면 구현을 하는 선 테스트, 후 구현의 흐름을 가집니다.

TDD의 개발 방식은 실패하는 테스트 -> 실패하는 테스트를 성공할 만큼의 기능 구현 -> 성공하는 테스트 -> 리펙토링 -> 실패하는 테스트와 성공하는 테스트 확인이라는 흐름을 반복합니다.



TDD의 장점

  • 테스트를 통과할 만큼만, 너무 많은 기능을 구현할 필요가 없습니다.
  • 테스트 코드가 추가되면서 검증하는 범위가 넓어짐 -> 기능 구현도 점점 넒어짐
  • 리펙토링 비용이 상대적으로 적음
  • 꾸준히 코드를 개선하므로 코드의 품질을 일정 부분 유지 가능
  • 코드 수정 후 바로 테스트 하므로 수정 결과를 빠르게 피드백 받을 수 있음


TDD의 단점

  • TDD방식이 낯설 수 있음
  • 팀원들간 사전 협의가 필요

항상 무조건 적으로 좋은 방법은 없습니다. 개발 환경과 상황에 맞게 좋은 개발방법을 선택하는 것이 중요합니다.




후기

슬라이스 테스트와 슬라이스 테스트에 필수적인 Mock에 대하여 공부했습니다. Mock을 잘 활용하여 계층간 연결을 끊음으로써 해당 계층에 집중할 수 있도록 테스트를 가정해야 겠습니다.




GitHub

private!

profile
오늘도 내일도 화이팅!

0개의 댓글