아래는 **5장. 테스트와 데이터 관리**에 대한 글입니다. 이 장은 스프링 부트에서 테스트 데이터를 효과적으로 생성하고 관리하는 방법을 다루며, 실무에서 흔히 사용하는 전략과 도구를 소개합니다. 실습 예제를 포함해 실질적인 이해를 돕고자 했습니다.

---

## 5장. 테스트와 데이터 관리

통합 테스트에서 데이터베이스와의 상호작용을 검증하려면 테스트 데이터의 생성과 관리가 필수적입니다. 데이터가 없거나 일관성이 부족하면 테스트 결과가 신뢰를 잃을 수 있습니다. 이 장에서는 테스트 데이터를 생성하는 전략, 트랜잭션 관리, 데이터 마이그레이션 도구 활용, 그리고 실제 데이터베이스와의 연동 테스트 방법을 살펴봅니다.

### 5.1 테스트 데이터 생성 전략

테스트 데이터는 테스트의 성공 여부를 좌우합니다. 효과적인 데이터 생성 전략은 다음과 같습니다:

- **수동 생성**: 테스트 코드 내에서 직접 데이터를 삽입합니다. 간단한 경우에 적합합니다.
  ```java
  @Test
  void testUserRetrieval() {
      User user = new User(1L, "Alice");
      userRepository.save(user);
      // 검증 로직
  }
  ```
- **픽스처(Fixture)**: 공통 데이터를 별도의 클래스나 파일로 관리합니다.
  ```java
  class TestFixtures {
      static User createDefaultUser() {
          return new User(1L, "Alice");
      }
  }
  ```
- **랜덤 데이터 생성**: 라이브러리(예: JavaFaker)를 사용해 다양한 데이터를 생성합니다.
  ```xml
  <dependency>
      <groupId>com.github.javafaker</groupId>
      <artifactId>javafaker</artifactId>
      <version>1.0.2</version>
      <scope>test</scope>
  </dependency>
  ```
  ```java
  @Test
  void testWithRandomData() {
      Faker faker = new Faker();
      User user = new User(null, faker.name().fullName());
      userRepository.save(user);
      // 검증 로직
  }
  ```

각 전략은 테스트의 목적과 복잡도에 따라 선택됩니다. 단순한 테스트에는 수동 생성이, 복잡한 시나리오에는 픽스처나 랜덤 데이터가 유용합니다.

### 5.2 @Transactional과 데이터 롤백

스프링 부트에서 통합 테스트를 실행할 때 데이터베이스 상태를 초기화하는 것은 중요합니다. `@Transactional` 애너테이션은 테스트 메서드 실행 후 데이터베이스 트랜잭션을 롤백해 원래 상태로 되돌립니다. 이를 사용하면 테스트 간 간섭을 방지할 수 있습니다.

예제:

```java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@Transactional
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void saveUser_rollsBackAfterTest() {
        User user = new User(1L, "Alice");
        userRepository.save(user);

        assertThat(userRepository.findById(1L)).isPresent();
        // 테스트 종료 후 롤백됨
    }
}
```

테스트가 끝나면 데이터가 자동으로 삭제되므로, 다음 테스트에 영향을 주지 않습니다. 단, 실제 데이터베이스에 반영하고 싶다면 `@Rollback(false)`를 추가할 수 있습니다.

### 5.3 Flyway 또는 Liquibase를 활용한 테스트 데이터 마이그레이션

운영 환경에서 데이터베이스 스키마와 초기 데이터를 관리하기 위해 Flyway나 Liquibase를 사용하는 경우, 테스트에서도 이를 활용할 수 있습니다. 이를 통해 테스트 환경을 운영과 동일하게 유지할 수 있습니다.

#### Flyway 설정
의존성 추가:

```xml
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
```

`src/test/resources/db/migration`에 SQL 파일 작성 (예: `V1__init_users.sql`):

```sql
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);

INSERT INTO users (id, name) VALUES (1, 'Alice');
INSERT INTO users (id, name) VALUES (2, 'Bob');
```

`application-test.properties`에 설정:

```properties
spring.flyway.locations=classpath:db/migration
```

테스트에서 Flyway가 자동으로 스키마와 데이터를 적용합니다.

#### Liquibase 설정
의존성 추가:

```xml
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>
```

`src/test/resources/db/changelog/db.changelog-master.yaml`:

```yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: test
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: bigint
                  constraints:
                    primaryKey: true
              - column:
                  name: name
                  type: varchar(255)
                  constraints:
                    nullable: false
        - insert:
            tableName: users
            columns:
              - column:
                  name: id
                  value: 1
              - column:
                  name: name
                  value: Alice
```

Flyway와 Liquibase는 테스트 데이터의 일관성을 보장하며, 복잡한 스키마 변경도 관리할 수 있습니다.

### 5.4 실제 데이터베이스와의 연동 테스트

H2나 Testcontainers 대신 실제 데이터베이스(예: MySQL, PostgreSQL)를 사용한 테스트가 필요할 때도 있습니다. 이는 운영 환경과 최대한 가까운 조건에서 테스트를 실행하고자 할 때 유용합니다.

설정 예제 (`application-test.properties`):

```properties
spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=testuser
spring.datasource.password=testpass
spring.jpa.hibernate.ddl-auto=update
```

테스트 코드:

```java
@SpringBootTest
class RealDatabaseIntegrationTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    @Transactional
    void testWithRealDatabase() {
        User user = new User(1L, "Alice");
        userRepository.save(user);

        User found = userRepository.findById(1L).orElse(null);
        assertThat(found).isNotNull();
        assertThat(found.getName()).isEqualTo("Alice");
    }
}
```

실제 데이터베이스 테스트는 속도가 느릴 수 있으므로, CI/CD 파이프라인에서는 별도의 단계로 분리하거나 제한적으로 사용하는 것이 좋습니다.

---

### 마무리

테스트 데이터 관리는 통합 테스트의 신뢰성과 효율성을 높이는 핵심입니다. 수동 생성, 픽스처, 랜덤 데이터 생성으로 유연성을 확보하고, `@Transactional`로 데이터 상태를 관리하며, Flyway나 Liquibase로 일관성을 유지할 수 있습니다. 실제 데이터베이스 연동은 운영 환경과의 차이를 줄이는 데 유용합니다. 다음 장에서는 성능과 부하 테스트를 다루며, 애플리케이션의 한계를 시험해 보겠습니다!

---

이 글은 데이터 관리의 다양한 접근법을 다루며, 실습 예제를 통해 실무 적용 가능성을 높였습니다. 추가 요청이나 수정 사항이 있다면 말씀해 주세요!