Почему JOOQ — идеальный инструмент для работы с БД при интеграционном тестировании API

от автора

Привет! Меня зовут Евгений, я 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.


ссылка на оригинал статьи https://habr.com/ru/articles/817253/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *