단위 테스트와 통합 테스트

햇슈·2025년 3월 17일

springboot

목록 보기
1/4

단위 테스트

Java 단위 테스트 작성에는 크게 2가지 라이브러리가 있는데

JUnit 5와 AssertJ 입니다

JUnit > 자바 단위 테스트를 위한 테스팅 프레임워크

AssertJ > 자바 테스트를 돕기 위한 문법 지원 라이브러리

assertEquals(a,b)assertThat(a).isEqualTo(b)라는 코드를 비교해보면

첫 번째는 a와 b가 동일한지 만 확인 가능하고

두 번째는 a가 실제 값, b가 예상 값이다 라는 것을 쉽게 확인 가능합니다.

따라서 저는 두 번째 문법을 더 선호합니다.(가 AssertJ 문법)

추가적으로 테스트 시에 실패 시 메세지를 통해서 예상값은 true 이지만 실제 값은 false 이다 뿐 아니라 어디에서 실패가 떳는지 추가적으로 원인이 무엇인지 도 상세하게 알려줍니다.

그러기에 혹시 JUnit을 사용하게 된다면 위와 같은 문법으로 사용하는 것을 적극 추천드립니다!

Test 코드 작성 시에는 given-when-then 패턴을 많이 사용합니다.

단위 테스트를 할 때 각 단계에 따라 나눠서 작성하게 되는데,

  • given : 어떤 데이터가 준비되었을 때
  • when : 어떠한 함수를 실행하면
  • then : 어떠한 결과가 나와야 한다.

라는 것을 정확히 나타내는 것입니다.

그래서 저는 test에 대해 위의 템플릿을 미리 만들어서 사용하는 것을 추천드립니다.

@ExtendWith(MockitoExtension.class)
/*
	개발자가 동작을 직접 제어할 수 있는 가짜 객체를 지원하는 테스트 프레임 워크
*/

public class AccountServiceTest extends DummyObject {
    @InjectMocks
    // @Mock 또는 @Spy 로 생성된 가짜 객체를 자동으로 주입시켜 주는 어노테이션
    // 즉, 아래에서 가짜로 만든 repository를 service에 주입시키는 것
    private AccountService accountService;

    @Mock
    // 가짜 객체를 만들어 반환해 주는 어노테이션
    private UserRepository userRepository;

    @Mock
    private AccountRepository accountRepository;

    @Spy
    // Stub 하지 않은 메소드들을 원본 메소드 그대로 사용하는 어노테이션
    // 참고로 ObjectMapper은 JSon 컨텐츠를 Java 객체로 deserialization or serialization
    // 하는 jackson 라이브러리 클래스
    // 참고로 OM은 생성 비용이 비싸서 bean 또는 static 으로 처리하는 것 이 좋습니다.
    private ObjectMapper om;

    @Test
    public void 계좌등록_test() throws Exception {
        // given
        Long userId = 1L;

        AccountReqDto.AccountSaveReqDto accountSaveReqDto = new AccountReqDto.AccountSaveReqDto();
        accountSaveReqDto.setNumber(1111L);
        accountSaveReqDto.setPassword(1234L);

        // stub 1
        // stub 는 의존성이 객체는 가짜 객체를 주입해 어떤 결과를 반환하라고 정해진 답변을
        // 준비시켜야 합니다. 
        // Mockito에서는 doReturn : 가짜 객체가 특정한 값을 반환해양 하는 경우
        // doNothing : 가짜 객체가 아무 것도 반환하지 않는 겨우
        // doThrow : 가짜 객체가 예외를 발생시키는 경우
        // 의 메소드를 제공합니다.
        User ssar = newMockUser(userId, "ssar", "쌀");
        when(userRepository.findById(any())).thenReturn(Optional.of(ssar));

        // stub 2
        when(accountRepository.findByNumber(any())).thenReturn(Optional.empty());

        // stub 3
        Account ssarAccount = newMockAccount(1L,1111L, 1000L, ssar);
        when(accountRepository.save(any())).thenReturn(ssarAccount);

        // when
        AccountResDto.AccountSaveResDto accountSaveResDto = accountService.계좌등록(accountSaveReqDto, userId);
        String responseBody = om.writeValueAsString(accountSaveResDto);
        System.out.println("테스트: " + responseBody);

        // then
        assertThat(accountSaveResDto.getNumber()).isEqualTo(1111L);

    }

}
  • 장점
    • 스프링은 내부적으로 스프링 컨텍스트를 캐싱해 두고 동일한 테스트 환겨이라면 재사용하는데, 그러기에 빠른 테스트가 가능하다.
    • 그리고 최소한의 컨텍스트만 가져오기에 더 적은 자원을 필요로 한다.

통합 테스트

여러 클래스, 여러 컴포넌트가 실제로 연결된 상태에서 테스트 한다가 다른 점
즉, 실제 환경과 유사하게 구성을 합니다.

단위 테스트에 비해서 속도가 느리고, 컨텍스트 로딩이나 I/O 등이 존재하기에 오버헤드가 존재합니다

  • 단위 테스트에 비해서 전체 흐름에서 분석을 하기에 어디서 문제인지 찾기가 어려워 저는 중요한 사항에 대해서는 단위 테스트를 더 선호합니다.
// UserService와 UserRepository를 함께 테스트
@SpringBootTest
class UserIntegrationTest {

    @Autowired
    // 차이점
    UserService userService;

    @Test
    void testUserRegistration() {
        User user = new User("John");
        userService.registerUser(user);
        User result = userService.findUser("John");
        
        assertNotNull(result);
        assertEquals("John", result.getName());
    }
}
  • 통합 테스트에서는 @Autowired 를 사용하게 되는데, 왜냐하면 실제 스프링 컨테이너가 모든 빈을 관리하고 진짜 빈을 주입 받아서 실제 서비스 환경과 동일하게 테스트 하기 위해 실제 빈을 주입합니다.
  • 그러기에 위에 @SpringBootTest 를 써서 전체 스프링 컨텍스트를 띄우게 되고 후에 @Autowired 로 실제 빈을 주입하는 형식입니다.
  • 또한 @Transactional을 주로 쓰는 것 같은데, 그 이유는 테스트 데이터 롤백 때문입니다!
  • DB에 실제로 insert 또는 delete 같은 작업을 하게 되는데, DB에 계속해서 데이터가 쌓이는 것을 방지하기 위해서 테스트가 끝날 때 자동으로 rollback 을 진행합니다.
  • 단위 테스트에서는 안 써도 됩니다! 왜냐하면 가짜 객체로 처리하기에 DB 에 접근을 안하게 됩니다.
profile
~ velog 새 단장중 ~

0개의 댓글