Junit 적용하기
테스트 코드 적용하기
이 영상을 보고 정리했습니다. 😀
이 글도 보았습니다.
+이 글 도 참고!!😀
테스트 코드 작성하는 이유
- 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);
...
}
}