더블 테스트
Service - DAO - DB
- DAO 더블 테스트
목적
DAO 더블 테스트 에서 테스트 후
DAO 실제 세계에 적용
더블 테스트의 기술 : mocking
- 주어진 클래스를 따로 테스트할 수 있게 해줍니다.
- 주어진 클래스와 종속성 사이에서 상호작용
- Configuration 최소화 , 종속성의 가용성
- DAO, DB, REAT API 등
>> Spring Boot는 Mocking 프레임워크 중 Mockito 지원
Mock 테스트 과정
- DAO를 위한 Mock 생성
- Service 에 Mock 주입
- Set up expectations(기대치)
- Execute test 메소드
- Assert results
- Verify 메소드 호출
@Mock
테스트를 할 때 필요한 실제 객체와 동일한 모의 객체를 만들어 테스트 효용성 높이기 위해 사용
더블 테스트를 하기 위한
테스트 클래스 생성
package com.luv2code.test;
@SpringBootTest(classes= MvcTestingExampleApplication.class) //패키지 경로가 메인이랑 다르기 때문에 메인클래스 설정
public class MockAnnotationTest
{
@Autowired
ApplicationContext context;
@Autowired
CollegeStudent studentOne;
@Autowired
StudentGrades studentGrades;
// DAO 더블 테스트
@Mock
private ApplicationDao applicationDao;
// @Mock or @Spy 에 대해서만 주입
@InjectMocks
private ApplicationService applicationService;
@BeforeEach
public void beforeEach(){
studentOne.setFirstname("java");
studentOne.setLastname("app");
studentOne.setEmailAddress("javapp@tistory.com");
studentOne.setStudentGrades(studentGrades);
}
@DisplayName("when % verify")
@Test
public void assertEqualsTestAddGrades(){
// stubbing
when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
.thenReturn(100.0);
assertEquals(100,applicationService.addGradeResultsForSingleClass(
studentOne.getStudentGrades().getMathGradeResults()));
// verify: dao 호출 확인 후...
verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
verify(applicationDao, times(1)).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
// times, 뒤에 메소드가 n 번 호출됐는지 검증
}
}
1.
@Mock 애노테이션을 이용해서 mock객체 생성
@Mock
private ApplicationDao applicationDao;
2.
@injectMocks
@Mock or @Spy 에 대해서만 주입
Service 내부 dao에 주입
@InjectMocks
private ApplicationService applicationService;
3. 4. 5. 6.
@DisplayName("when % verify")
@Test
public void assertEqualsTestAddGrades(){
// stubbing, getMathGradeResults()의 값을 100으로 설정
when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
.thenReturn(100.0);
assertEquals(100,applicationService.addGradeResultsForSingleClass(
studentOne.getStudentGrades().getMathGradeResults()));
// verify: dao 호출 확인 후...
verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
verify(applicationDao, times(1)).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
// times, 뒤에 메소드가 n 번 호출됐는지 검증
}
.thenReturn(n) : applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()) 값에 n을 넣어줌.
@MockBean
스프링 컨텍스트에 Mock객체를 빈으로 추가
@Mock and @InjectMocks 대신 사용
include @Mock
@InjectMocks 대신 @Autowired 사용
// DAO 더블 테스트
//@Mock
@MockBean
private ApplicationDao applicationDao;
// @Mock or @Spy 에 대해서만 주입
//@InjectMocks
@Autowired
private ApplicationService applicationService;
테스트 추가
@DisplayName("Find GPA")
@Test
public void assertEqualsTestFindGpa(){
when(applicationDao.findGradePointAverage(studentGrades.getMathGradeResults())).thenReturn(88.31);
assertEquals(88.31, applicationService.findGradePointAverage(studentOne.getStudentGrades().getMathGradeResults()));
}
@DisplayName("Not Null")
@Test
public void testAssertNotNull(){
when(applicationDao.checkNull(studentGrades.getMathGradeResults())).thenReturn(true);
assertNotNull(applicationService.checkNull(studentOne.getStudentGrades().getMathGradeResults()),"not null 이여아 한다.");
}
@DisplayName("Throw runtime error")
@Test
public void throwRuntimeError(){
// null 객체 생성
CollegeStudent nullStudent = (CollegeStudent) context.getBean("collegeStudent");
// null 일때 예외를 던진다.
doThrow(new RuntimeException()).when(applicationDao).checkNull(nullStudent);
// 예외가 RuntimeException 인지
assertThrows(RuntimeException.class, ()->{
applicationService.checkNull(nullStudent);
});
// applicationDao.checkNull() 메소드가 1번 실행되었는지
verify(applicationDao, times(1)).checkNull(nullStudent);
}
@DisplayName("Multiple Stubbing")
@Test
public void stubbingConsecutiveCalls(){
CollegeStudent nullStudent = (CollegeStudent) context.getBean("collegeStudent");
when(applicationDao.checkNull(nullStudent)).thenThrow(new RuntimeException())
.thenReturn("예외 두번 실행");
// 예외가 RuntimeException 인지
assertThrows(RuntimeException.class, ()->{
applicationService.checkNull(nullStudent);
});
assertEquals("예외 두번 실행",applicationService.checkNull(nullStudent));
// dao 와 service 에서 .checkNull을 두번 실행
verify(applicationDao, times(2)).checkNull(nullStudent);
}
리플렉션
ReflectionTestUtils
non-public 한 필드를 직접 get/set
비공개 메소드 호출 가능
추가적으로 엔티티에는 @Setter 메소드를 선언하면 좋지 않습니다. 엔티티 혹은 객체는 불변한 성질을 가져야 하기 때문입니다. 그래서 set 함수가 없을 경우 ReflectionTestUtils.setField(..) 을 통해 값을 설정할 수 있습니다.
CollegeStudent.java
private int id;
private String getFirstNameAndId(){
return getFirstname()+" "+getId();
}
@SpringBootTest(classes = MvcTestingExampleApplication.class)
public class ReflectionTestUtilsTest {
@Autowired
ApplicationContext context;
@Autowired
CollegeStudent studentOne;
@Autowired
StudentGrades studentGrades;
@BeforeEach
public void studentBeforeEach() {
studentOne.setFirstname("java");
studentOne.setLastname("app");
studentOne.setEmailAddress("javapp@daum.net");
studentOne.setStudentGrades(studentGrades);
ReflectionTestUtils.setField(studentOne, "id", 1);
ReflectionTestUtils.setField(studentOne, "studentGrades",
new StudentGrades(new ArrayList<>(Arrays.asList(
100.0, 85.0, 76.5, 50.0
))));
}
@Test
public void getPrivateField(){
assertEquals(1, ReflectionTestUtils.getField(studentOne,"id"));
}
@Test
public void invokePrivateMethod(){
assertEquals("javapp 1",
ReflectionTestUtils.invokeMethod(studentOne,"getFirstNameAndId"),
"private 메소드 호출 실패");
}
}
assertEquals(1, ReflectionTestUtils.getField(studentOne,"id"));
assertEquals("javapp 1",
ReflectionTestUtils.invokeMethod(studentOne,"getFirstNameAndId"),
"private 메소드 호출 실패");
'Back-end > 테스트' 카테고리의 다른 글
Spring Boot MVC Controller Test (컨트롤러 테스트) (0) | 2022.11.05 |
---|---|
Spring Boot MVC 데이터베이스 통합 테스트 @Sql (0) | 2022.10.29 |
Spring Boot Unit Testing Support - 1 (0) | 2022.10.10 |
TDD 테스트 주도 개발 연습 (0) | 2022.10.05 |
JUnit - 코드 커버리지 테스트 (0) | 2022.09.25 |
댓글