Java

[JAVA] Spring을 이용한 테스트 코드 작성 방법 (단위 테스트, 통합 테스트)

TaeHuiLee 2025. 1. 11. 08:00
반응형

소프트웨어 개발에서 테스트 코드는 단순히 오류를 찾는 도구를 넘어, CI/CD(Continuous Integration/Continuous Deployment) 파이프라인과 개발 생산성 향상에 핵심적인 역할을 합니다.
이 글에서는 JPA를 사용한 DB 연결 후 테스트를 작성하는 방법을 단계별로 알아보겠습니다.


1. 🌱 단위 테스트와 통합 테스트의 차이

✅ 단위 테스트(Unit Test)

  • 목적: 메서드, 클래스 등 작은 단위의 코드가 올바르게 동작하는지 검증.
  • 도구: JUnit, Mockito 등.
  • 실행 방식:
    • @ExtendWith, @Mock, @InjectMocks 등을 활용하여 외부 의존성을 모킹(Mock).
    • 테스트 환경에서 빠르게 검증.

✅ 통합 테스트(Integration Test)

  • 목적: 여러 구성 요소(Controller, Service, Repository 등)가 올바르게 협력하는지 검증.
  • 도구: Spring Boot Test, H2 Database.
  • 실행 방식:
    • @SpringBootTest를 활용하여 애플리케이션 컨텍스트를 로드.
    • 실제 데이터베이스와 연결하거나 테스트용 메모리 데이터베이스 사용.

2. 🌟 준비 사항

2.1 의존성 추가 (Maven 기준)

테스트를 위한 주요 라이브러리를 추가합니다.

의존성을 pom.xml 파일에 추가합니다.

<dependencies>
    <!-- Spring Boot Starter Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- H2 Database (테스트용) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2.2 테스트 환경 설정 (application.yml)

테스트 전용 데이터베이스를 설정합니다. 여기서는 H2를 사용합니다.

application-test.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true

3. 🛠 단위 테스트 작성하기

단위 테스트에서는 데이터베이스 대신 Mock을 활용합니다. 예제로 간단한 UserService의 단위 테스트를 작성해 볼게요.

3.1 User 엔티티 및 리포지토리

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    // Getters and Setters
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

3.2 UserService

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(String name, String email) {
        User user = new User();
        user.setName(name);
        user.setEmail(email);
        return userRepository.save(user);
    }
}

3.3 UserService 단위 테스트 작성

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void testCreateUser() {
        // Given
        String name = "John";
        String email = "john@example.com";
        User user = new User();
        user.setName(name);
        user.setEmail(email);

        when(userRepository.save(any(User.class))).thenReturn(user);

        // When
        User createdUser = userService.createUser(name, email);

        // Then
        assertEquals(name, createdUser.getName());
        assertEquals(email, createdUser.getEmail());
        verify(userRepository, times(1)).save(any(User.class));
    }
}

@ExtendWith(MockitoExtension.class)

  • 설명:
    • JUnit 5(=JUnit Jupiter)와 Mockito를 통합해 사용할 때 필요한 어노테이션입니다.
    • Mockito에서 제공하는 Mock 객체를 테스트 클래스에 주입할 수 있도록 설정합니다.
  • 사용 시점:
    • 단위 테스트에서 Mockito를 사용하여 테스트 대상을 모킹(Mocking)할 때 사용합니다.

@Mock

  • 설명:
    • Mockito에서 제공하는 어노테이션으로, 가짜 객체(Mock)를 생성합니다.
    • 실제 객체 대신 Mock 객체를 사용하여 외부 의존성을 제거하고 코드의 특정 부분만 테스트할 수 있습니다.
  • 사용 시점:
    • 리포지토리, API 호출 등 실제 객체와 상호작용하지 않고 원하는 동작을 시뮬레이션할 때.

@InjectMocks

  • 설명:
    • Mockito에서 제공하는 어노테이션으로, 테스트 대상 클래스에 Mock 객체를 주입합니다.
    • @Mock으로 생성된 Mock 객체를 테스트 대상 클래스의 생성자나 필드에 자동으로 주입합니다.
  • 사용 시점:
    • 테스트 대상 클래스에 의존성 주입(DI)이 필요한 경우.

4. 🛠 통합 테스트 작성하기

통합 테스트에서는 실제 데이터베이스와 연결된 환경에서 테스트를 실행합니다.

4.1 통합 테스트 설정

@SpringBootTest@Transactional 어노테이션을 활용해 통합 테스트를 작성합니다.

@SpringBootTest
@Transactional
 @ActiveProfiles("test") // test 프로파일 활성화
public class UserIntegrationTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void testSaveAndFindUser() {
        // Given
        User user = new User();
        user.setName("Alice");
        user.setEmail("alice@example.com");

        // When
        userRepository.save(user);
        Optional<User> foundUser = userRepository.findById(user.getId());

        // Then
        assertTrue(foundUser.isPresent());
        assertEquals("Alice", foundUser.get().getName());
        assertEquals("alice@example.com", foundUser.get().getEmail());
    }
}

@SpringBootTest

  • 설명:
    • Spring Boot 통합 테스트를 실행하기 위한 어노테이션입니다.
    • 애플리케이션 전체 컨텍스트를 로드하고 테스트를 수행합니다.
  • 사용 시점:
    • 여러 Bean 간의 상호작용 및 애플리케이션 전반적인 통합 테스트를 실행할 때.

@Transactional

  • 설명:
    • 테스트 실행 중 데이터 변경 사항을 롤백하도록 설정합니다.
    • 테스트마다 새로운 트랜잭션이 생성되고, 테스트가 끝나면 데이터베이스 상태를 초기화합니다.
  • 사용 시점:
    • 데이터베이스를 사용하는 통합 테스트에서 DB 상태를 초기화해야 할 때.

@ActiveProfiles

  • 설명:
    • 특정 Spring Profile을 활성화하도록 설정합니다.
    • 이를 통해 테스트 환경, 개발 환경 등 환경별 설정을 분리할 수 있습니다.
  • 사용 시점:
    • application-test.yml과 같은 테스트 전용 설정 파일을 적용하고 싶을 때.

5. 🎯 정리: 단위 테스트와 통합 테스트의 핵심 비교

특징 단위 테스트 통합 테스트
목적 특정 단위의 독립적 검증 시스템의 통합된 흐름 검증
외부 의존성 Mock 객체 사용 실제 데이터베이스 사용
실행 속도 빠름 상대적으로 느림
어노테이션 @ExtendWith(MockitoExtension.class) @SpringBootTest, @Transactional

단위 테스트는 빠르게 개발 주기를 반복할 때 유용하며, 통합 테스트는 실제 환경과의 상호작용을 검증하는 데 필수적입니다.

반응형