← Назад к вопросам
Проверял ли Unit-тесты при работе с базами данных
2.2 Middle🔥 181 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Unit-тесты при работе с базами данных
Краткий ответ
Да, я многократно писал и проверял unit-тесты для работы с базами данных. Это критическая часть разработки, так как ошибки в коде, работающем с БД, часто приводят к потере данных и серьёзным bagам в production.
Стратегия тестирования с БД
1. Разделение на уровни тестов
Unit Tests (быстрые, изолированные):
└─ Бизнес-логика без БД (mocks, stubs)
Integration Tests (медленнее, с реальной БД):
└─ Работа с реальной БД
└─ Проверка SQL запросов
└─ Проверка транзакций
E2E Tests (самые медленные):
└─ Полный цикл: API → БД → ответ
Подход 1: Unit-тесты без БД (Mocking)
Самый быстрый способ — использовать mock/stub для репозиториев:
public interface UserRepository {
User findById(Long id);
void save(User user);
void delete(Long id);
}
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserProfile(Long id) {
User user = userRepository.findById(id);
if (user == null) {
throw new UserNotFoundException("User not found");
}
user.setLastAccess(LocalDateTime.now());
return user;
}
}
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
public void testGetUserProfile_Success() {
Long userId = 1L;
User expectedUser = new User(userId, "John Doe");
when(userRepository.findById(userId)).thenReturn(expectedUser);
User result = userService.getUserProfile(userId);
assertNotNull(result);
assertEquals("John Doe", result.getName());
verify(userRepository).findById(userId);
}
@Test
public void testGetUserProfile_NotFound() {
Long userId = 999L;
when(userRepository.findById(userId)).thenReturn(null);
assertThrows(UserNotFoundException.class, () -> {
userService.getUserProfile(userId);
});
}
}
Преимущества Unit-тестов с mocks:
- Очень быстрые (выполняются за миллисекунды)
- Изолированные (не зависят от БД)
- Детерминированные (всегда одинаковый результат)
- Легко параллелизируются
Подход 2: Integration-тесты с реальной БД
Для тестирования самих запросов к БД используем реальную БД:
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager entityManager;
@Test
public void testSaveAndFindUser() {
User user = new User();
user.setName("John");
user.setEmail("john@test.com");
User savedUser = userRepository.save(user);
entityManager.flush();
User foundUser = userRepository.findById(savedUser.getId()).orElse(null);
assertNotNull(foundUser);
assertEquals("John", foundUser.getName());
}
@Test
public void testFindByEmail() {
User user = new User();
user.setName("Jane");
user.setEmail("jane@test.com");
userRepository.save(user);
entityManager.flush();
entityManager.clear();
User foundUser = userRepository.findByEmail("jane@test.com");
assertNotNull(foundUser);
assertEquals("Jane", foundUser.getName());
}
@Test
public void testDeleteUser() {
User user = new User();
user.setName("Tom");
user.setEmail("tom@test.com");
User savedUser = userRepository.save(user);
entityManager.flush();
userRepository.delete(savedUser);
entityManager.flush();
Optional<User> foundUser = userRepository.findById(savedUser.getId());
assertTrue(foundUser.isEmpty());
}
}
Технологии для Integration-тестов:
@Testcontainers
public class UserRepositoryWithTestcontainersTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:14")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Test
public void testWithRealPostgres() {
// Тест с реальной PostgreSQL в Docker
}
}
Подход 3: Custom Repository для проверки SQL
@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
@Autowired
private EntityManager entityManager;
@Override
public List<User> findActiveUsers() {
String jpql = "SELECT u FROM User u WHERE u.active = true ORDER BY u.name";
return entityManager.createQuery(jpql, User.class)
.getResultList();
}
}
@DataJpaTest
public class UserRepositoryCustomTest {
@Autowired
private UserRepository userRepository;
@Test
public void testFindActiveUsers() {
User activeUser = new User("John", true);
User inactiveUser = new User("Jane", false);
userRepository.saveAll(Arrays.asList(activeUser, inactiveUser));
List<User> activeUsers = userRepository.findActiveUsers();
assertEquals(1, activeUsers.size());
assertEquals("John", activeUsers.get(0).getName());
}
}
Лучшие практики при тестировании БД
1. Изолируйте тесты друг от друга
@Transactional
public class UserRepositoryTest {
// Тесты не влияют друг на друга благодаря откату
}
2. Используйте TestData builders
public class UserBuilder {
private String name = "Default User";
private String email = "default@test.com";
private boolean active = true;
public UserBuilder withName(String name) {
this.name = name;
return this;
}
public User build() {
return new User(name, email, active);
}
}
User user = new UserBuilder()
.withName("John")
.build();
3. Проверяйте ограничения целостности данных
@Test
public void testForeignKeyConstraint() {
User user = new User();
user.setName("John");
user.setDepartmentId(9999L);
assertThrows(DataIntegrityViolationException.class, () -> {
userRepository.save(user);
});
}
@Test
public void testUniqueConstraint() {
User user1 = new User();
user1.setEmail("duplicate@test.com");
userRepository.save(user1);
User user2 = new User();
user2.setEmail("duplicate@test.com");
assertThrows(DataIntegrityViolationException.class, () -> {
userRepository.save(user2);
});
}
4. Проверяйте транзакции
@Test
public void testTransactionRollback() {
try {
userService.createUserAndAssignToGroup(user, group);
} catch (Exception e) {
// При ошибке всё откатывается
}
assertFalse(userRepository.findById(user.getId()).isPresent());
}
Пример полной стратегии тестирования
- Unit-тесты (без БД): 150 тестов, выполняются за 2 секунды
- Integration-тесты (с БД): 40 тестов, выполняются за 30 секунд
- E2E тесты: 10 тестов, выполняются за 2 минуты
Покрытие:
- Бизнес-логика: 95%
- SQL запросы: 85%
- Весь цикл: 60%
Практические рекомендации
- Большинство тестов должны быть unit-тесты (быстрые, без БД)
- Integration-тесты для критичного функционала (работа с данными)
- Используйте @Transactional для изоляции тестов
- Избегайте использования production БД в тестах
- Параллелизируйте выполнение unit-тестов
- Измеряйте покрытие кода (минимум 80-90%)
- Пишите тесты перед кодом (TDD) для лучшего дизайна