ballqs 님의 블로그
[Spring] Test 코드 작성(stub , mock) 본문
오늘은 강의를 들으면서 테스트 코드 작성하는 것에 대해서 메모했다.
기본적인 테스트 코드 작성 방법
MyMath.java 코드 작성
public class MyMath {
public int calculateSum(int[] numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
}
테스트 코드 작성
public class MyMathTest {
private MyMath math = new MyMath();
@Test
void calculateSum_ThreeMemberArray() {
// Absence of failure is success
// Test Condition or Assert
assertEquals(6 , math.calculateSum(new int[] {1,2,3}));
}
}
실행시켜보면 테스트가 성공한 것을 알수 있다.
여기서 assert 란?
인수를 검증하고 조건에 맞지 않은 경우 IllegalArgumentException 또는 IllegalStateException를 발생시킨다.
이 부분은 조건문을 단순화하고 반복적인 코드를 줄이는 역할을 한다.
메소드 종류
Method | 설명 |
assertEquals(x , y) | x와 y의 값이 일치한지 확인한다. |
assertArrayEquals(a , b) | 배열 a와 b가 같은지 확인한다. |
assertFalse(x) | x가 false인지 확인한다. |
assertTrue(x) | x가 true인지 확인한다. |
assertTrue(message , condition) | condition이 true이면 message 표시 |
assertNull(o) | o가 null인지 확인한다. |
assertNotNull(o) | o가 null이 아닌지 확인한다. |
assertSame(ox , oy) | ox와 oy가 같은 객체인지 확인한다. |
assertNotSame(ox , oy) | ox와 oy가 같은 객체가 아닌지 확인한다. |
assertfail() | 테스트 실패처리 |
Test 코드의 Annotation종류
Annotataion | 설명 |
@BeforeAll | 모든 테스트가 시작하기 전에 실행 |
@AfterAll | 모든 테스트가 끝나고 나서 실행 |
@BeforeEach | 테스트별 실행 전에 실행 |
@AfterEach | 테스트별 실행이 끝나고 실행 |
사용 예제
public class MyMathTest {
private MyMath math = new MyMath();
@BeforeEach
void beforeEach() {
// 테스트별 시작전
System.out.println("Before each");
}
@AfterEach
void afterEach() {
// 테스트별 종료후
System.out.println("After each");
}
@BeforeAll
static void beforeAll() {
// 모든 테스트의 시작 전
// 클래스 레벨 메서드이기에 static를 붙여야함
System.out.println("Before all");
}
@AfterAll
static void afterAll() {
// 모든 테스트가 끝나고 나서
// 클래스 레벨 메서드이기에 static를 붙여야함
System.out.println("After all");
}
@Test
void calculateSum_ThreeMemberArray() {
System.out.println("calculateSum_ThreeMemberArray 실행");
assertEquals(6 , math.calculateSum(new int[] {1,2,3}));
}
@Test
void calculateSum_ZeroMemberArray() {
System.out.println("calculateSum_ZeroMemberArray 실행");
assertEquals(0 , math.calculateSum(new int[] {}));
}
@Test
void test() {
System.out.println("test 실행");
assertArrayEquals(new int[] {1,2} , new int[] {1,2});
}
}
Stub이란?
인스턴스화하여 구현한 가짜 객체(Dummy, 기능 구현 x)를 이용해 실제로 동작하는 것처럼 보이게 만드는 객체
해당 인터페이스나 클래스를 최소한으로 구현하여 테스트에서 호출된 요청에 대해 미리 준비해둔 답변을 응답하는 것
DataService.java 작성
interface DataService {
int[] retrieveAllData();
}
SomeBusinessImpl.java 작성
public class SomeBusinessImpl {
private DataService dataService;
SomeBusinessImpl(DataService dataService) {
super();
this.dataService = dataService;
}
public int findTheGreatestFromAllData() {
int[] data = dataService.retrieveAllData();
int greatestValue = Integer.MIN_VALUE;
for (int value : data) {
if (value > greatestValue) {
greatestValue = value;
}
}
return greatestValue;
}
}
여기서 보면 retrieveAllData 메서드가 아직 미구현이라는 것을 알수 있다.
stub를 이용하여 테스트 하기 위해
stub 코드를 작성
class DataServiceStub1 implements DataService {
@Override
public int[] retrieveAllData() {
return new int[]{25 , 15 , 5};
}
}
테스트 코드 작성
public class SomeBusinessImplStubTest {
@Test
void test() {
DataServiceStub1 dataServiceStub = new DataServiceStub1();
SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceStub);
int result = businessImpl.findTheGreatestFromAllData();
assertEquals(25 , result);
}
}
여기서 stub의 문제점이 생긴다.
- DataService에 새로운 기능이 추가될때마다 DataServiceStub1에도 새로 업데이트 해줘야 하는 문제점
- stub을 이용하면 많은 시나리오 테스트하기가 어려운 점
- 추가적인 시나리오를 만들고 싶다 생각하면 똑같이 만들어서 업데이트 해줘야하는 점
이런 문제점을 해결하고자 mock에 대해서 알아보았다.
Mock 이란?
호출에 대한 기대를 명세하고, 내용에 따라 동작하도록 프로그래밍된 객체
DataService.java 작성
interface DataService {
int[] retrieveAllData();
}
SomeBusinessImpl.java 작성
public class SomeBusinessImpl {
private DataService dataService;
SomeBusinessImpl(DataService dataService) {
super();
this.dataService = dataService;
}
public int findTheGreatestFromAllData() {
int[] data = dataService.retrieveAllData();
int greatestValue = Integer.MIN_VALUE;
for (int value : data) {
if (value > greatestValue) {
greatestValue = value;
}
}
return greatestValue;
}
}
Annotataion 없이 작성
public class SomeBusinessImplMockTest {
@Test
void findTheGreatestFromAllData_basicScenario() {
// mock
DataService dataServiceMock = mock(DataService.class);
// 해당 메서드에서 thenReturn이라는 결과값을 리턴받는 것으로 가정함
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {25, 15 , 5});
SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock);
// 위의 리턴값을 바탕으로 밑의 메서드 행위를 검증함
int result = businessImpl.findTheGreatestFromAllData();
System.out.println(result);
// 예상하는 값은 25이며 결과값 또한 25로 테스트 통과
assertEquals(25 , result);
}
}
Annotatation 으로 작성
@ExtendWith(MockitoExtension.class)
public class SomeBusinessImplMockTest {
// mock지정
@Mock
private DataService dataServiceMock;
// mock이 적용되고 행위검증할 클래스가 무엇인지 정하는 것으로 추정
@InjectMocks
private SomeBusinessImpl businessImpl;
@Test
void anotationTest() {
// anotataion으로 Test하는 방법
when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {25, 15 , 5});
assertEquals(25 , businessImpl.findTheGreatestFromAllData());
}
}
Stub과 Mock의 차이
stub은 상태 검증(state verification) 을 사용하고 Mock 오브젝트는 행위 검증(behavior verification) 사용한다.
상태 검증 : 메서드가 수행된 후, 객체의 상태를 확인해 올바르게 동작했는지 확인하는 검증법
행위 검증 : 메소드의 리턴 값으로 판단 할 수 없는 경우 , 특정 동작을 수행하는지 확인하는 검증법
'코딩 공부 > Spring' 카테고리의 다른 글
[Spring] Spring Security 기초 사용법 (0) | 2024.08.30 |
---|---|
[Spring] AOP (0) | 2024.08.29 |
[Spring] JPA Save() vs SaveAll() vs Bulk Insert (0) | 2024.08.26 |
[Spring] PasswordEncoder 암호화 및 bcrypt 암호화 (0) | 2024.08.23 |
[Spring] Naver Open API 사용방법 (0) | 2024.08.20 |