아래는 **3장. 통합 테스트**에 대한 글입니다. 이 장은 스프링 부트에서 통합 테스트의 개념을 설명하고, 실무에서 자주 사용하는 설정과 예제를 다룹니다. 단위 테스트와의 차이점을 강조하며 실습 중심으로 작성했습니다.

---

## 3장. 통합 테스트

단위 테스트가 개별 구성 요소를 독립적으로 검증한다면, 통합 테스트는 애플리케이션의 여러 부분이 함께 잘 작동하는지 확인하는 과정입니다. 스프링 부트에서는 컨트롤러, 서비스, 리포지토리가 데이터베이스나 외부 시스템과 상호작용하며 기대한 결과를 내는지 테스트할 수 있습니다. 이 장에서는 통합 테스트의 필요성, 설정 방법, 그리고 실습 예제를 다룹니다.

### 3.1 통합 테스트의 개념과 필요성

통합 테스트(Integration Test)는 애플리케이션의 여러 모듈 또는 계층이 상호작용할 때 발생할 수 있는 문제를 발견하는 데 목적이 있습니다. 단위 테스트가 모킹으로 의존성을 제거한다면, 통합 테스트는 실제 의존성을 포함해 전체 흐름을 검증합니다. 예를 들어, REST API 요청이 컨트롤러를 거쳐 서비스와 리포지토리를 호출하고, 데이터베이스에서 데이터를 가져오는 과정을 테스트한다고 생각해 봅시다. 이 과정에서 발생할 수 있는 설정 오류, 데이터 불일치, 예외 처리를 확인할 수 있습니다.

통합 테스트가 필요한 이유는 다음과 같습니다:
- **현실적인 동작 확인**: 실제 환경과 유사한 조건에서 동작을 검증합니다.
- **의존성 간 문제 탐지**: 모킹으로는 발견하기 어려운 상호작용 문제를 찾습니다.
- **배포 전 신뢰성 확보**: 단위 테스트만으로는 놓칠 수 있는 통합된 시스템의 안정성을 보장합니다.

스프링 부트는 통합 테스트를 쉽게 작성할 수 있도록 강력한 도구를 제공하며, 이를 활용하는 방법을 배워보겠습니다.

### 3.2 @SpringBootTest 애너테이션 이해

스프링 부트에서 통합 테스트를 작성할 때 가장 중요한 애너테이션은 `@SpringBootTest`입니다. 이 애너테이션은 스프링 애플리케이션 컨텍스트를 로드하며, 실제 운영 환경과 유사한 설정으로 테스트를 실행합니다. 기본 사용법은 다음과 같습니다:

```java
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MyApplicationTests {

    @Test
    void contextLoads() {
        // 애플리케이션 컨텍스트가 로드되는지 확인
    }
}
```

`@SpringBootTest`는 다양한 옵션을 제공합니다:
- `webEnvironment`: 웹 애플리케이션 환경을 설정합니다.
  - `MOCK`: 모킹된 웹 환경(기본값).
  - `RANDOM_PORT`: 실제 서버를 띄우고 랜덤 포트 사용.
  - `DEFINED_PORT`: 지정된 포트 사용.
- `classes`: 특정 클래스만 로드해 테스트 속도를 높일 수 있습니다.

예를 들어, 웹 환경에서 테스트하려면 다음과 같이 설정합니다:

```java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {
    // 테스트 코드
}
```

### 3.3 테스트용 데이터베이스 설정 (H2, Testcontainers)

통합 테스트에서는 데이터베이스와의 상호작용을 검증해야 하므로, 테스트용 데이터베이스 설정이 중요합니다. 스프링 부트는 이를 위해 두 가지 인기 있는 방법을 지원합니다: **H2 인메모리 데이터베이스**와 **Testcontainers**.

#### H2 인메모리 데이터베이스
H2는 가볍고 빠른 인메모리 데이터베이스로, 설정이 간단합니다. `pom.xml`에 의존성을 추가합니다:

```xml
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>
```

그리고 `application-test.properties`에 설정을 추가합니다:

```properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
```

#### Testcontainers
Testcontainers는 Docker를 사용해 실제 데이터베이스(예: MySQL, PostgreSQL)를 테스트 환경에서 실행합니다. 설정이 조금 복잡하지만, 운영 환경과 더 가까운 테스트를 제공합니다. 의존성을 추가합니다:

```xml
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>
```

테스트 클래스에서 사용 예제:

```java
import org.testcontainers.containers.MySQLContainer;

@SpringBootTest
@Testcontainers
class MySqlIntegrationTest {

    @Container
    private static final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
            .withDatabaseName("test")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void mysqlProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysql::getJdbcUrl);
        registry.add("spring.datasource.username", mysql::getUsername);
        registry.add("spring.datasource.password", mysql::getPassword);
    }

    @Test
    void testWithRealDatabase() {
        // 테스트 코드
    }
}
```

### 3.4 REST API 통합 테스트 실습

이제 실제 REST API를 호출하는 통합 테스트를 작성해 봅시다. `UserController`가 있다고 가정하고, 이를 테스트합니다:

```java
@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}/name")
    public String getUserName(@PathVariable Long id) {
        return userService.getUserName(id);
    }
}
```

통합 테스트 코드:

```java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private UserRepository userRepository;

    @Test
    void getUserName_returnsName() {
        // 테스트 데이터 준비
        User user = new User(1L, "Alice");
        userRepository.save(user);

        // API 호출
        ResponseEntity<String> response = restTemplate.getForEntity("/users/1/name", String.class);

        // 결과 검증
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo("Alice");
    }
}
```

`TestRestTemplate`은 실제 HTTP 요청을 보내고 응답을 검증하는 데 유용합니다. `@Transactional`을 추가하면 테스트 후 데이터가 롤백되어 깔끔하게 유지됩니다.

---

### 마무리

통합 테스트는 스프링 부트 애플리케이션의 전체 흐름을 검증하며, 단위 테스트로는 확인할 수 없는 현실적인 문제를 발견할 수 있습니다. `@SpringBootTest`, H2, Testcontainers를 활용하면 다양한 환경에서 테스트를 실행할 수 있습니다. 다음 장에서는 테스트를 더 효율적으로 작성하고 관리하는 유틸리티와 도구를 살펴보겠습니다. 통합 테스트를 통해 여러분의 애플리케이션에 더 큰 신뢰를 더해보세요!

---

이 글은 실습 예제와 함께 통합 테스트의 핵심을 다루었으며, 실무 적용 가능성을 높였습니다. 추가 요청이나 수정 사항이 있다면 말씀해 주세요!