← Назад к вопросам

Как в Unit-тестах протестировать синтаксис базы данных

2.0 Middle🔥 201 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как в Unit-тестах протестировать синтаксис базы данных

Тестирование синтаксиса SQL в Unit-тестах — важная часть разработки, которая помогает выловить ошибки на ранних этапах. Рассмотрим несколько подходов.

1. Встроенная База Данных (H2, Derby)

Это классический подход для Unit-тестов. Используются in-memory базы, которые быстро запускаются и не требуют конфигурации:

public class UserRepositoryTest {
    @BeforeEach
    void setUp() {
        // H2 автоматически создаёт in-memory БД
        Connection conn = DriverManager.getConnection(
            "jdbc:h2:mem:testdb;MODE=PostgreSQL"
        );
    }
    
    @Test
    void testInsertUserQuery() throws SQLException {
        String sql = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)";
        try (PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setInt(1, 1);
            stmt.setString(2, "John");
            stmt.setString(3, "john@example.com");
            
            int rows = stmt.executeUpdate();
            assertEquals(1, rows);
        }
    }
}

2. Testcontainers для Реальной БД

Если нужна именно та БД, которую используешь в продакшене (PostgreSQL, MySQL):

public class UserRepositoryPostgresTest {
    @Container
    static PostgreSQLContainer<?> postgres = 
        new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");
    
    @Test
    void testComplexQuery() throws SQLException {
        String sql = "SELECT u.id, u.name, COUNT(o.id) as order_count " +
                     "FROM users u LEFT JOIN orders o ON u.id = o.user_id " +
                     "GROUP BY u.id, u.name HAVING COUNT(o.id) > ?"; 
        
        try (Connection conn = postgres.createConnection("");
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            stmt.setInt(1, 0);
            ResultSet rs = stmt.executeQuery();
            
            assertTrue(rs.next());  // проверяем, что запрос выполнился
        }
    }
}

3. SQL Парсеры и Линтеры

Для проверки синтаксиса SQL можно использовать специализированные библиотеки:

public class SqlSyntaxTest {
    @Test
    void testSqlSyntaxWithJSQLParser() {
        String sql = "SELECT * FROM users WHERE id = 1";
        
        try {
            Statement statement = CCJSqlParserUtil.parse(sql);
            assertNotNull(statement);
        } catch (JSQLParserException e) {
            fail("SQL синтаксис неверный: " + e.getMessage());
        }
    }
}

Добавь зависимость:

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.6</version>
    <scope>test</scope>
</dependency>

4. JUnit с Spring Boot Test

Для Spring приложений есть встроенная поддержка:

@SpringBootTest
@DataJpaTest  // только слой данных
class UserRepositorySpringTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testCustomQuery() {
        User user = new User("John", "john@example.com");
        entityManager.persist(user);
        entityManager.flush();
        
        // Используется твой UserRepository с @Query
        List<User> result = userRepository.findByNameStartingWith("J");
        assertEquals(1, result.size());
    }
}

5. Создание Схемы БД в Тесте

public class DatabaseSchemaTest {
    private Connection conn;
    
    @BeforeEach
    void setUp() throws SQLException {
        conn = DriverManager.getConnection("jdbc:h2:mem:testdb");
        
        // Создаём таблицу
        String createTable = "CREATE TABLE users (" +
                           "id INT PRIMARY KEY, " +
                           "name VARCHAR(100) NOT NULL, " +
                           "email VARCHAR(100) UNIQUE, " +
                           "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" +
                           ")";
        
        try (Statement stmt = conn.createStatement()) {
            stmt.execute(createTable);
        }
    }
    
    @Test
    void testInsertWithUniqueConstraint() throws SQLException {
        String insert = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)";
        
        try (PreparedStatement stmt = conn.prepareStatement(insert)) {
            stmt.setInt(1, 1);
            stmt.setString(2, "John");
            stmt.setString(3, "john@example.com");
            stmt.executeUpdate();
            
            // Проверяем, что дублирование не сработает
            stmt.setInt(1, 2);
            stmt.setString(2, "Jane");
            stmt.setString(3, "john@example.com");
            
            assertThrows(SQLException.class, stmt::executeUpdate);
        }
    }
}

6. Проверка Производительности Запроса

@Test
@Tag("performance")
void testQueryPerformance() throws SQLException {
    // Вставляем много данных
    String insert = "INSERT INTO users (id, name) VALUES (?, ?)";
    
    long startTime = System.currentTimeMillis();
    
    try (PreparedStatement stmt = conn.prepareStatement(insert)) {
        for (int i = 0; i < 10000; i++) {
            stmt.setInt(1, i);
            stmt.setString(2, "User" + i);
            stmt.addBatch();
        }
        stmt.executeBatch();
    }
    
    long duration = System.currentTimeMillis() - startTime;
    assertTrue(duration < 5000, "Вставка 10k записей должна быть < 5s");
}

7. Тестирование Миграций

public class FlywayMigrationTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
    
    @Test
    void testAllMigrationsApply() {
        Flyway flyway = Flyway.configure()
                .dataSource(postgres.getJdbcUrl(), 
                           postgres.getUsername(),
                           postgres.getPassword())
                .load();
        
        MigrateResult result = flyway.migrate();
        assertEquals(5, result.migrationsExecuted);  // проверяем количество миграций
    }
}

Лучшие Практики

  1. Используй In-Memory БД для скорости — H2 быстрее Testcontainers
  2. Используй Testcontainers для реальной БД — если нужна точная эмуляция
  3. Парсируй SQL перед выполнением — ловишь ошибки рано
  4. Тестируй ограничения (constraints) — UNIQUE, NOT NULL, FK
  5. Изолируй тесты БД — каждый тест должен быть независим
  6. Используй транзакции для откатов — чтобы не засорять БД
  7. Проверяй и граничные случаи — NULL, пустые строки, очень большие значения

Комбинированный подход: быстрые Unit-тесты с H2 + интеграционные тесты с реальной БД в CI.