1. Java 단위 테스트 작성 준비
a. 필요한 라이브러리
Java 단위테스트 작성에는 크게 2가지 라이브러리가 사용됩니다.
- JUnit5 : 자바 단위 테스트를 위한 테스팅 프레임워크
- AssertJ : 자바 테스트를 돕기 위해 다양한 문법을 지원하는 라이브러리
JUnit만으로도 단위 테스트를 충분히 작성할 수 있습니다. 하지만 JUint에서 제공하는 assertEquals()와 같은 메서드는 AssertJ가 주는 메서드에 비해 가독성이 떨어집니다. 그렇게 때문에 순수 Java 애플리케이션에서 단위 테스트를 위해 JUnit5와 AssertJ 조합이 많이 사용됩니다.
💡 AssertJ가 가독성이 좋은 이유
1. JUnit의 assertEquals()
@Test void testAddition() { int result = 3 + 5; assertEquals(8, result); }
- 기댓값(expected)과 실제값(actual)의 순서가 헷갈릴 수 있습니다.
-> assertEquals(expected, actual) 순서이지만, 실수로 반대로 작성할 가능성이 있습니다.
- 자연어처럼 읽히지 않습니다.
-> assertEquals(8, result) → “8이 결과와 같다”보다 “결과가 8이어야 한다”가 더 자연스럽게 읽힘
2. AssertJ의 assertThat()
@Test void testAddition() { int result = 3 + 5; assertThat(result).isEqualTo(8); }
- 자연스럽게 읽힙니다.
-> "결과가 8이어야 한다"처럼 직관적입니다,
- 메서드 체이닝이 가능하여 다양한 조건을 쉽게 추가 가능합니다.
assertThat(result) .isNotNull() .isGreaterThan(5) .isLessThan(10);
b. given/when/then 패턴
given-when-then 패턴이란 1개의 단위 테스트를 3가지 단계로 나누어 처리하는 패턴으로, 각각의 단계는 다음을 의미합니다.
- given(준비) : 어떠한 데이터가 준비되었을 때
- when(실행) : 어떠한 함수를 실행하면
- then(검증) : 어떠한 결과가 나와야 한다.
c. 테스트 코드 작성 공통 규칙
@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
// given
// when
// then
}
@Test는 해당 메서드가 단위 테스트임을 명시하는 어노테이션입니다. JUnit은 테스트 패키지 하위의 @Test 어노테이션이 붙은 메서드를 단위 테스트로 인식하여 실행시킵니다. 이 상태로 실행하면 테스트 이름이 함수 이름으로 default로 지정되는데, 위의 코드에서는 @DisplayName 어노테이션을 사용하여 읽기 좋은 다른 이름을 부여했습니다.
또한, 테스트 코드는 앞서 설명한 given-when-then 구조로 흔히 작성되는데, 단위 테스트 내에 주석으로 이 단계를 명시해 주면 읽기 좋은 테스트 코드를 작성할 수 있습니다.
2. Mockito
a. Mockito란
Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜 객체를 지원하는 테스트 라이브러리입니다. 일반적으로 Spring으로 웹 애플리케이션을 개발하면, 여러 객체들의 의존성이 생깁니다. 이러한 의존성은 단위 테스트를 작성하는데 어렵게 하기 때문에 이를 해결하기 위해 가짜 객체를 주입시켜 주는 Mockito 라이브러리를 활용할 수 있습니다. Mockito를 활용하면 가짜 객체에 원하는 결과를 Stub 하여 단위 테스트를 진행할 수 있습니다. 물론 라이브러리 도구가 필요없다면 사용하지 않는 것이 가장 좋다고 생각합니다.
b. Mockito 작성법
b-1. Mockito 객체 의존성 주입
Mockito에서 가짜 객체의 의존성 주입을 위해서는 크게 3가지 어노테이션이 사용됩니다.
- @Mock : 가짜 객체를 만들어 반환해주는 어노테이션
- @Spy : Stub하지 않은 메서드들은 원본 메서드 그대로 사용하는 어노테이션
- @InjectMocks : @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션
예를 들어 UserController에 대한 단위 테스트를 작성하고자 할 때, UserService를 사용하고 있다면 @Mock 어노테이션을 통해 가짜 UserService를 만들고, @InjectMocks를 통해 UserController에 이를 주입시킬 수 있습니다.
b-2. Stub로 결과 처리
앞서 설명하였듯 의존성이 있는 객체는 가짜 객체를 주입하여 어떤 결과를 반환하라고 정해진 답변을 준비시켜야 합니다. Mockito에서는 다음과 같은 stub 메서드를 제공합니다.
- doReturn() : 가짜 객체가 특정한 값을 반환해야 하는 경우
- doNothing() : 가짜 객체가 아무 것도 반환하지 않는 경우
- doThrow() : 가짜 객체가 예외를 발생시키는 경우
b-3. Mockito와 JUnit의 결합
Mockito도 테스팅 라이브러리이기 때문에 JUnit과 결합되기 위해서는 별도의 작업이 필요합니다. 기존의 JUnit4에서 Mockito를 활용하기 위해서는 클래스 어노테이션으로 @RunWith(MockitoJUnitRunner.class)를 붙여주어야 연동이 가능했습니다. 하지만 SpringBoot 2.2.0부터 공식적으로 JUnit5를 지원함에 따라, 이제부터는 @ExtendWith(MockitoExtension.class)를 사용해야 결합이 가능합니다.
3. 단위 테스트 작성 예시
@Transactional(readOnly = true)
public UserInfoResponse getUserInfo(Long userId) {
User user = getUserById(userId);
Reward reward = rewardRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException(ErrorCode.REWARD_NOT_FOUND));
return new UserInfoResponse(user.getNickname(), reward.getExperience(), reward.getNutrition());
}
위와 같은 유저 조회 코드에 대한 테스트 코드를 작성해보겠습니다.
a. 단위 테스트 작성 준비
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
앞서 설명하였듯 JUnit5와 Mockito를 연동하기 위해서는 @ExtendWith(MockitoExtension.class)를 사용해야 합니다. 이를 클래스 어노테이션으로 붙여 다음과 같이 테스트 클래스를 작성할 수 있습니다.
b. 유저 조회 Test
@BeforeEach
void setUp() {
testUser = User.builder()
.email(USER_EMAIL)
.nickname(USER_NICKNAME)
.provider(AuthProvider.KAKAO)
.profileImage("profile.jpg")
.build();
testUser.setId(USER_ID);
testReward = Reward.builder()
.user(testUser)
.experience(0)
.nutrition(0)
.build();
}
우선 유저를 조회하기 위해서는 유저 객체가 필요합니다. 그렇기 때문에 테스트 시작 전에 @BeforeEach를 사용하여 testUser와 testReward 객체를 초기화하는 역할을 합니다.
- testUser : User 객체를 빌더 패턴을 사용하여 생성하고, 이메일, 닉네임, 인증 제공자(Kakao), 프로필 이미지 등을 설정합니다. 이후 setId() 메서드로 사용자 ID를 설정합니다.
- testReward : Reward 객체를 빌더 패턴을 사용하여 생성하고, testUser를 연결한 후 경험치와 영양값을 0으로 설정합니다.
@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
// given
when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser));
when(rewardRepository.findById(USER_ID)).thenReturn(Optional.of(testReward));
// when
// then
}
given 단계에서는 Mockito를 사용하여 userRepository와 rewardRepository의 findById 메서드가 특정 값을 반환하도록 미리 설정합니다. userRepository에서 USER_ID로 데이터를 조회하면 testUser라는 사용자 정보를 반환하고, rewardRepository에서는 USER_ID로 데이터를 조회하면 testReward라는 보상 정보를 반환하도록 지정합니다.
@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
// given
when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser));
when(rewardRepository.findById(USER_ID)).thenReturn(Optional.of(testReward));
// when
UserInfoResponse response = userService.getUserInfo(USER_ID);
// then
}
when 단계에서는 userService.getUserInfo(USER_ID) 메서드를 호출하여, 이전에 설정한 userRepository와 rewardRepository에서 반환된 사용자 정보와 보상 정보를 기반으로 UserInfoResponse 객체를 받아옵니다.
@Test
@DisplayName("유저 정보 조회 성공")
void getUserInfo_Success() {
// given
when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser));
when(rewardRepository.findById(USER_ID)).thenReturn(Optional.of(testReward));
// when
UserInfoResponse response = userService.getUserInfo(USER_ID);
// then
assertNotNull(response);
assertEquals(testUser.getNickname(), response.nickname());
}
then 단계에서는 assertNotNull을 사용해 response 객체가 null이 아님을 확인합니다. 이는 userService.getUserInfo(USER_ID) 호출이 정상적으로 응답을 반환했는지를 검증하는 과정입니다. 또한, assertEquals()를 사용해 response.nickname()이 testUser.getNickname()과 일치하는지 비교합니다. 즉, 반환된 사용자 정보의 닉네임이 예상한 값과 정확히 일치하는지 확인하는 검증입니다.
'Programming > etc' 카테고리의 다른 글
[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 작성 (1) (0) | 2024.12.06 |
---|