Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как протестировать базу данных в Java
Тестирование БД — критически важная часть разработки. Покажу несколько подходов от простого к сложному.
Способ 1: Unit тесты с H2 in-memory БД
H2 — быстрая в памяти БД, идеальна для быстрых тестов.
Зависимости
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope> <!-- Только для тестов!
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Конфиг для тестов
# src/test/resources/application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop # Создаёт и удаляет schema для каждого теста
show-sql: true
Тест
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;
@DataJpaTest
@TestPropertySource(locations = "classpath:application-test.yml")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository; // Spring автоматически инъектирует
@Test
void testSaveUser() {
// ARRANGE
User user = new User();
user.setName("John");
user.setEmail("john@example.com");
// ACT
User saved = userRepository.save(user);
// ASSERT
assertNotNull(saved.getId());
assertEquals("John", saved.getName());
}
@Test
void testFindByEmail() {
// ARRANGE
User user = new User();
user.setName("Jane");
user.setEmail("jane@example.com");
userRepository.save(user);
// ACT
Optional<User> found = userRepository.findByEmail("jane@example.com");
// ASSERT
assertTrue(found.isPresent());
assertEquals("Jane", found.get().getName());
}
@Test
void testFindByEmailNotFound() {
// ACT
Optional<User> found = userRepository.findByEmail("nonexistent@example.com");
// ASSERT
assertFalse(found.isPresent());
}
}
Способ 2: Интеграционные тесты с TestContainers
TestContainers позволяет запускать реальную БД в Docker контейнере.
Зависимости
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
Тест с PostgreSQL
import org.springframework.boot.test.context.SpringBootTest;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
@SpringBootTest
public class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void testSaveUserWithPostgres() {
User user = new User();
user.setName("Alice");
user.setEmail("alice@example.com");
User saved = userRepository.save(user);
assertNotNull(saved.getId());
assertEquals("Alice", saved.getName());
}
@Test
void testComplexQuery() {
// Тестируем сложные запросы с настоящей БД
userRepository.save(new User("Alice", "alice@example.com"));
userRepository.save(new User("Bob", "bob@example.com"));
userRepository.save(new User("Charlie", "charlie@example.com"));
List<User> users = userRepository.findAll();
assertEquals(3, users.size());
}
}
Способ 3: Сквозное тестирование (E2E) с @SpringBootTest
@SpringBootTest
@Transactional // Откатывает изменения после каждого теста
public class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentRepository paymentRepository;
@Test
void testCreateOrderEndToEnd() {
// Тестируем весь путь: сервис -> репозиторий -> БД
Order order = new Order();
order.setCustomerId(1L);
order.setAmount(BigDecimal.valueOf(100.0));
orderService.createOrder(order);
// Проверяем что записалось в БД
Optional<Order> saved = orderRepository.findById(order.getId());
assertTrue(saved.isPresent());
assertEquals(1L, saved.get().getCustomerId());
}
}
Способ 4: Тестирование native queries
@DataJpaTest
public class NativeQueryTest {
@Autowired
private EntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
@Transactional
void testNativeQuery() {
// Сохраняем тестовые данные
User user = new User();
user.setName("TestUser");
user.setEmail("test@example.com");
userRepository.save(user);
// Выполняем native query
@SuppressWarnings("unchecked")
List<User> results = (List<User>) entityManager
.createNativeQuery("SELECT * FROM users WHERE email = ?1", User.class)
.setParameter(1, "test@example.com")
.getResultList();
assertFalse(results.isEmpty());
assertEquals("TestUser", results.get(0).getName());
}
}
Способ 5: Тестирование с миграциями (Flyway)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource("classpath:application-test.yml")
public class MigrationTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
void testMigrationApplied() {
// Проверяем что таблица создана
String sql = "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'users'";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
assertEquals(1, count);
}
}
Способ 6: Fixture и test data
@DataJpaTest
public class UserRepositoryWithFixturesTest {
@Autowired
private UserRepository userRepository;
// Fixture — подготовка данных
@BeforeEach
void setUp() {
userRepository.save(new User("Alice", "alice@example.com"));
userRepository.save(new User("Bob", "bob@example.com"));
userRepository.save(new User("Charlie", "charlie@example.com"));
}
@Test
void testFindAllUsers() {
List<User> users = userRepository.findAll();
assertEquals(3, users.size());
}
@Test
void testDeleteUser() {
User alice = userRepository.findByEmail("alice@example.com").get();
userRepository.delete(alice);
assertEquals(2, userRepository.count());
}
}
Способ 7: Специальные аннотации для БД
// Кастомная аннотация для повторного использования конфигурации
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@DataJpaTest
@TestPropertySource("classpath:application-test.yml")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
public @interface RepositoryTest { }
// Использование
@RepositoryTest
public class CustomUserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void test() { }
}
Способ 8: Тестирование с несколькими БД (Polyglot)
@SpringBootTest
@Testcontainers
public class PolyglotDatabaseTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");
@Container
static GenericContainer<?> redis =
new GenericContainer<>("redis:7").withExposedPorts(6379);
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.redis.host", redis::getHost);
registry.add("spring.redis.port", redis::getFirstMappedPort);
}
@Autowired
private UserRepository userRepository;
@Autowired
private RedisTemplate<String, User> redisTemplate;
@Test
void testPersistenceAndCache() {
User user = userRepository.save(new User("Test", "test@example.com"));
redisTemplate.opsForValue().set("user:1", user);
assertNotNull(redisTemplate.opsForValue().get("user:1"));
}
}
Способ 9: Проверка constraints и索引
@DataJpaTest
public class ConstraintTest {
@Autowired
private UserRepository userRepository;
@Test
void testUniqueEmailConstraint() {
User user1 = new User();
user1.setName("Alice");
user1.setEmail("duplicate@example.com");
userRepository.save(user1);
User user2 = new User();
user2.setName("Bob");
user2.setEmail("duplicate@example.com");
// Должна выбросить ошибку уникальности
assertThrows(DataIntegrityViolationException.class, () -> {
userRepository.save(user2);
userRepository.flush(); // Принудительная flush для проверки
});
}
}
Best Practices тестирования БД
-
Используй @DataJpaTest для repository тестов
@DataJpaTest //快 быстро, загружает только слой данных -
Используй @SpringBootTest для интеграционных тестов
@SpringBootTest // Полный контекст -
Откатывай изменения с @Transactional
@Transactional // Откатит после теста -
Предпочитай H2 для unit тестов
- Быстрая в памяти БД
- Не нужно контейнеров
-
Используй TestContainers для интеграционных
- Реальная БД
- Тестируешь реальные constraints и features
-
Подготавливай данные в setUp()
@BeforeEach void setUp() { // Fixture } -
Тестируй как позитивные, так и негативные кейсы
// Успешное сохранение // Дублирование (constraint) // Null значения // Граничные случаи -
Включай SQL логирование в тестах
logging: level: org.hibernate.SQL: DEBUG
Рекомендуемая стратегия
Unit Tests (H2):
- Repository методы
- Query логика
- Быстрые
Integration Tests (TestContainers):
- Всё вместе
- Миграции
- Constraints
- Реальная БД
E2E Tests (@SpringBootTest):
- Сервисы
- Контроллеры
- Полный путь
В production используй:
- H2 для unit (быстро)
- PostgreSQL в TestContainers для интеграции (реально)
- Отдельное окружение для smoke tests (перед деплоем)
Это даст тебе быстрый feedback в разработке и уверенность в production.