3 minute read

테스트 코드 적용하기

이 영상을 보고 정리했습니다. 😀

이 글도 보았습니다.

+이 글 도 참고!!😀

테스트 코드 작성하는 이유

  • Side Effect를 줄일 수 있음

Junit

  • Unit Test로 많이 사용하는것 같음
  • 단정문(Assert)로 수행 결과를 확인할 수 있음
  • Spring Boot 2.2버전 부터 JUnit5가 적용됌

JUnit Jupiter

  • 구현체

JUnit Platform

  • Test를 실행하기 위한 뼈대

JUnit Vintage

  • TestEngine API구현체

JUnit LifeCycle Annotation

  • @Test : 테스트를 명시한다.
  • @BeforeEach : 메소드가 시작되기 전에 실행되어야 하는 메소드
  • @AfterEach : 메소드가 시작된 후 실행되어야 하는 메소

JUnit Main Annotation

  • @SpringBootTest : 통합 테스트 용도
    • 하위에 있는 모든 Bean을 스캔한다.
  • @ExtendWith : @RunWith가 ExtendWith로 변경됨
    • 테스트할 클래스를 지정해서 사용할 수 있음
        @ExtendWith(SpringExtension.class)
        @Import({ProductDataHandlerImpl.class, ProductServiceImpl.class})
      
  • @WebMvcTest(Class명.class) : 컨트롤러와 연관된 Bean이 모두 로드됨
    • 서비스 객체까지 다 Bean으로 로드되는듯
    • ExtendWith를 쓰면 사용한 서비스 객체를 @Autowired로 주입받게됌
  • @Autowired about Mockbean : 컨트롤러의 API를 테스트하는 용도인 MockMvc 객체를 주입받음
    • perform() 메소드를 활용해 컨트롤러의 동작을 확인할 수 있음
    • andExpect(), andDo(), andReturn() 등 메소드를 같이 활용
  • @MockBean : 테스트할 클래스에서 주입 받고 있는 객체에 대해 가짜 객체를 생성하는 어노테이션
    • Controller에 Service 를 주입받는다고 가정하면 이를 가짜 객체로 생성해준다. (Mock으로 만들어준다.)
          //아래 처럼 셋팅하겠다. 
          given(productDataHandler.getProductEntity("123"))
              .willReturn(new Product("123", "pen", 2000, 3000));
    
  • @AutoConfigureMockMvc : spring.test.mockmvc의 설정을 로드하면서 MockMvc의 의존성을 자동으로 주입
    • MockMvc 클래스는 REST API 테스트를 할 수 있는 클래스
  • @Import : 필요한 클래스를 Configuration으로 만들어서 사용할 수 있음

단위 테스트

  • 프로젝트에 필요한 모든 기능에 대한 테스트를 각각 진행하는것을 의미
  • F.I.R.S.T 원칙이 있다고 한다. (생략)

직접 해보자.

  • Mokito를 이용한 Mock객체 생성
  • 아래 테스트를 실행하게 되면 NullPointException이 발생한다. (WriteArticleServiceImpl.writeArticle() 메서드에서 IdGenerator와 ArticleDao을 구현한 객체를 사용하기 때문)
public class WriteArticleServiceImpl {
    private IdGenerator idGenerator;
    private ArticleDao articleDao;

    public Article writeArticle(Article article) {
        Integer id = idGenerator.getNextId();
        article.setId(id);
        articleDao.insert(article);
        return article;
    }
    // idGenerator와 articleDao에 대한 setter
    ...
}
@Test
public void writeArticle() {
    WriteArticleServiceImpl writeArticleService = new WriteArticleServiceImpl();
    Article article = new Article();
    Article writtenArticle = writeArticleService.writeArticle(article);
       
    assertNotNull(writtenArticle);
    assertNotNull(writtenArticle.getId());
}
  • 그래서 아래처럼 Mokito를 이용한다.
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import org.junit.Test;

public class WriteArticleServiceImplTest {

    @Test
    public void writeArticle() {
        // mock 객체 생성
        ArticleDao mockedDao = mock(ArticleDao.class);
        IdGenerator mockedGenerator = mock(IdGenerator.class);
       
        WriteArticleServiceImpl writeArticleService = new WriteArticleServiceImpl();
        writeArticleService.setArticleDao(mockedDao);
        writeArticleService.setIdGenerator(mockedGenerator);
       
        Article article = new Article();
        Article writtenArticle = writeArticleService.writeArticle(article);
       
        assertNotNull(writtenArticle);
    }
}

Mock객체의 메서드 호출 검증

  • Mock 객체의 메서드가 올바르게 실행되는 지 확인
  • Mokito.verify() 메서드와 Mock 객체의 메서드를 함께 사용
  • Mock 객체가 만들어지면 Mock 객체의 메서드 호출 모두 기억, 어떤 메서드 호출이든 검증 가능
@Test
public void writeArticle() {
        // mock 객체 생성
    ArticleDao mockedDao = mock(ArticleDao.class);
    IdGenerator mockedGenerator = mock(IdGenerator.class);
       
    WriteArticleServiceImpl writeArticleService = new WriteArticleServiceImpl();
    writeArticleService.setArticleDao(mockedDao);
    writeArticleService.setIdGenerator(mockedGenerator);
       
    Article article = new Article();
    Article writtenArticle = writeArticleService.writeArticle(article);
       
    assertNotNull(writtenArticle);
    verify(mockedGenerator).getNextId();
    verify(mockedDao).insert(article);
}

원하는 값을 리턴하는 스텁 생성

  • Mockito.mock()을 이용해서 생성한 객체의 메서드는 리턴 타입이 객체인 경우 null을 리턴하고 기본 데이터 타입인 경우 기본 값을 리턴한다.
Article writtenArticle = writeArticleService.writeArticle(article);
assertNotNull(writtenArticle);
assertNotNull(writtenArticle.getId()); // 에러 발생
public Article writeArticle(Article article) {
    Integer id = idGenerator.getNextId(); // 객체를 리턴하므로 null을 리턴한다. 
    article.setId(id);
    articleDao.insert(article);
    return article;
}
  • 스텁을 이용해보자. when - then 형식을 띄고 있다.
    • Mock 객체의 메서드가 알맞은 값을 리턴하는 스텁을 만들 수 있는 기능
IdGenerator mockedGenerator = mock(IdGenerator.class);
when(mockedGenerator.getNextId()).thenReturn(new Integer(1));

WriteArticleServiceImpl writeArticleService = new WriteArticleServiceImpl();
writeArticleService.setIdGenerator(mockedGenerator);

Article article = new Article();
Article writtenArticle = writeArticleService.writeArticle(article);

assertNotNull(writtenArticle);
assertNotNull(writtenArticle.getId());
verify(mockedGenerator).getNextId();
  • mokito에서 doReturn/thenReturn 으로 스텁을 생성할 수 있다.
  • thenReturn
    • 메소드를 실제 호출하지만 리턴 값은 임의로 정의 할 수 있다.
    • 메소드 작업이 오래 걸릴 경우 끝날때까지 기다림
  • doReturn
    • 메소드를 실제 호출하지 않으면서 리턴 값을 임의로 정의 할 수 있다.
    • 실제 메소드를 호출하지 않기 때문에 대상 메소드에 문제점이 있어도 알수가 없다.
//아래 위 비교
when(spyWhenService.callServerAPI("1")).thenReturn(true);
when(spyWhenService.callServerAPI("2")).thenReturn(false);
doReturn(true).when(spyWhenService).callServerAPI("1");
doReturn(false).when(spyWhenService).callServerAPI("2");
  • 아래 처럼 사용할 수 있다.
  • 작업이 오래 걸리는 경우 또는 다른 시스템과 연동을 해야할 경우 doReturn이 좋겠지만 실제 메소드를 호출하지 않기 때문에 되도록이면 thenReturn을 사용한다.
// given
WhenService realWhenService = new WhenService();
WhenService spyWhenService = spy(realWhenService);

// console에 좀 오래 걸리는 작업입니다 라는 메세지가 출력된다.
when(spyWhenService.callServerAPI("2")).thenReturn(false);

// console에 좀 오래 걸리는 작업입니다 라는 메세지가 출력되지 않는다.
doReturn(true).when(spyWhenService).callServerAPI("1");

// when
String result1 = spyWhenService.getSystemInfo("1");

// then
assertEquals("system123", result1);

thenThrow() 이용한 예외 발생

  • Mock 객체의 메서드 호출시 예외를 발생시키고 싶을 때가 있는데, 이런 경우에는 thenThrow() 메서드를 사용하면 된다.
when(mockedDao.insert(article)).thenThrow(new RuntimeException("invalid title"));

Mock 어노테이션을 이용한 코드 단순화

  • Mockito.mock() 메서드를 이용해서 Mock 객체를 생성하는 코드가 다소 성가시게 느껴진다면, @Mock 어노테이션을 이용해서 Mock 객체를 생성할 수 있다.
  • JUnit 4 버전의 경우 @RunWith 어노테이션에서 MockitoJUnit44Runner.class를 값으로 지정해주면 된다.
@RunWith(MockitoJUnit44Runner.class)
public class WriteArticleServiceImplTest {

    @Mock Authenticator authenticator;
    @Mock ArticleDao mockedDao;
    @Mock IdGenerator mockedGenerator;
   
    @Test
    public void setup() {
        when(authenticator.authenticate(anyString(), eq("password"))).thenReturn(null);
        ...
    }
}

Categories:

Updated: