Привет! Меня зовут Евгений, я SDET-специалист в SimbirSoft. Хочу поделиться примером того, как я автоматизировал тестирование API, заменив встроенные JDBC-средства на JOOQ. И расскажу, почему считаю это лучшим решением.
Все началось с того, что передо мной поставили задачу автоматизировать тестирование API с проверкой данных в БД. Так как проект только начинался, а я один отвечал за эту часть работы, то надо было сделать всё с нуля. Мне хотелось сделать все идеально (удобно, понятно, масштабируемо, с удобной поддержкой кода). Получилось все, кроме одного — масштабирование сверки данных из БД. Об этом и пойдет речь. А в конце вы найдете ссылку на исходный код.

Помним, что проект только начинал разрабатываться, поэтому база данных была небольшой и не сильно разветвленной. Поэтому мне показалось уместным использовать встроенные JDBC-средства для этой задачи.
На просторах интернета есть много информации на эту тему, в своей же статье я хотел бы рассказать о своем пути использования и улучшения данного метода с помощью дополнительного фреймворка JOOQ.
Используем JDBC
Для того чтобы подключиться к базе, создается подключение:
connection = DriverManager.getConnection(url, username, password);
После делается запрос в базу данных:
Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(script);
И получение данных:
while (resultSet.next()) { Integer id = resultSet.getInt(“id”); String name = resultSet.getString(“name”); }
В моем случае база данных имела много таблиц, которые продолжали изменяться и дополняться, что приводило к изменению уже созданных запросов. Я подумал, как можно улучшить эту систему, и в результате сделал генерацию строки запроса.
public interface CompanyFields { String COMPANY_TABLE = "company c"; String COMPANY_ID_FIELD = "c.id"; String COMPANY_NAME_FIELD = "c.name"; }
public class QueryBuilder implements AirplaneFields, CompanyFields, ModelFields, FlightFields, PassengerFields { private final StringBuffer query = new StringBuffer(); public String build() { return query.toString(); } public QueryBuilder select() { query.append("SELECT "); return this; } public QueryBuilder all() { query.append("*"); return this; } public QueryBuilder from() { query.append(" FROM "); return this; } public QueryBuilder where() { query.append(" WHERE "); return this; } public QueryBuilder equals() { query.append(" = "); return this; } public QueryBuilder addElement(long integer) { query.append(integer); return this; } public QueryBuilder companyTable() { query.append(COMPANY_TABLE); return this; } public QueryBuilder companyId() { query.append(COMPANY_ID_FIELD); return this; } }
В итоге создание запроса выглядело так:
new QueryBuilder().select().all().from().companyTable() .where().companyId().equals().addElement(id).build()
А также получение итогового файла стало проще:
new CompanyDto(resultSet.getInt(COMPANY_ID_FIELD), resultSet.getString(COMPANY_NAME_FIELD));
Система мне показалась довольно удобной, пока не начали появляться новые таблицы в очень большом количестве. И тогда я вернулся к тому, с чего начинал и о чем продолжал думать всё это время.
А что, так можно было?
Я задавался вопросом: масштабировал ли кто-нибудь ранее такую систему и, если и делал, то при помощи чего? Практически все статьи были про удобство Spring Data JPA для бэкенда. Я не хотел повторять уже сделанную работу бэкенда, поэтому такое решение было не самым лучшим. Но также во время поисков я наткнулся на статью о другом фреймворке для работы с базой данных. Он работает по схожему с JDBC принципом (формирование запросов) — и это JOOQ.
Если посмотреть на то, как составляется запрос в JOOQ и тот запрос, который я создал выше, можно увидеть большое сходство. Однако классы для описания таблиц в БД были намного сложнее того, что я и так уже сделал. Поэтому я этот вариант отбросил.
И насколько сильным было моё удивление, когда я узнал, что эти классы для JOOQ можно не прописывать, а подтягивать напрямую из самой базы данных!
Для этого нужно добавить несколько зависимостей:
<!-- For test --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>${postgres.driver.version}</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.3.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq</artifactId> <version>${jooq.version}</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq-meta</artifactId> <version>${jooq.version}</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq-codegen</artifactId> <version>${jooq.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
Здесь можно взять jooq-config.xml файл для подгрузки таблиц из базы данных (для других баз и конфигураций):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.18.0.xsd"> <jdbc> <driver>org.postgresql.Driver</driver> <url>jdbc:postgresql://localhost:5432/jooq_DB</url> <user>xuser</user> <password>password</password> </jdbc> <generator> <name>org.jooq.codegen.JavaGenerator</name> <database> <name>org.jooq.meta.postgres.PostgresDatabase</name> <inputSchema>public</inputSchema> <includes>.*</includes> </database> <target> <packageName>org.example.jooq.db.autocreated</packageName> <directory>.\src\test\java</directory> </target> </generator> </configuration>
И методы для подключения к базе данных:
public final class DatabaseConnector { private DatabaseConnector() { } private static Connection connection = getConnection(); private static DSLContext context; public static synchronized Connection getConnection() { if (connection == null) { try { // Получение данных, для подключения к БД String dbDriverClass = ParametersProvider .getProperty("jdbc.driver"); String dbUrl = ParametersProvider .getProperty("jdbc.url"); String dbUsername = ParametersProvider .getProperty("jdbc.username"); String dbPassword = ParametersProvider .getProperty("jdbc.password"); Class.forName(dbDriverClass) connection = DriverManager.getConnection(dbUrl, dbUsername, dbPassword); } catch (SQLException | ClassNotFoundException e) { throw new RuntimeException("Connection error", e); } } return connection; } public static DSLContext getContext() { if (context == null) { context = DSL.using(connection, SQLDialect.POSTGRES); try { GenerationTool.generate( Files.readString( Path.of("src\\test\\resources\\jooq-config.xml") ) ); } catch (Exception ignored) { } } return context; } }
И вуаля, получил я все таблицы! Ну а дальше их можно просто использовать в удобном виде:
Dtoшка:
@Data @NoArgsConstructor @AllArgsConstructor public class CompanyDto { private Integer id; private String name; }
Методы для получения данных:
public CompanyDto getCompanyById(Integer id) { return context.select() .from(Company.COMPANY) .where(Company.COMPANY.ID.eq(id)) .fetch() .map(this::getCompanyDtoByRecord) .get(0); } public CompanyDto getCompanyDtoByRecord(Record record) { return new dto.CompanyDto( record.getValue(Company.COMPANY.ID), record.getValue(Company.COMPANY.NAME) ); }
Сам JOOQ позволяет строить абсолютно любые запросы, в том числе на создание, изменение, удаление как самих баз, так и данных внутри них.
Вывод
В интернете есть много статей, в которых сравнивают разные способы работы с базой данных. Но я так и не увидел ни одной, где бы сказали, что это идеальное решение.
По моему мнению, для интеграционного тестирования API JOOQ будет как раз таким. Простой способ взаимодействия с БД (через запросы), автоматическая подгрузка таблиц (в условиях, когда не наш код отвечает за формирование базы данных), простая поддержка (при изменении базы данных, все необходимые места для изменения в коде будут подсвечены). То есть удобно, понятно, масштабируемо и с удобной поддержкой кода. Как и требовалось
Напишите в комментариях, какое бы вы использовали решение данной задачи?
Спасибо за внимание!
Больше авторских материалов для SDET-специалистов от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.
