Как использовать @BeforeAll в моих тестах io.quarkus.redis.datasource.RedisDataSource

У меня есть набор тестовых классов в Quarkus, которые тестируют классы моего приложения, использующие io.quarkus.redis.datasource.RedisDataSource

Я хочу запустить @BeforeAll для каждого тестового класса, чтобы повторно заполнить базу данных Redis известными данными. Метод BeforeAll должен быть статическим:

  @BeforeAll
  static void resetTestData() {
    // use a static version of io.quarkus.redis.datasource.RedisDataSource; here 
    // so I can do something like:
    // GraphCommands<String> gc = ds.graph();
    // gc.graphQuery(redisdbkey, "CREATE (:XXX {key:'abcd', name:'fred'})");
  }

Я не могу понять, как создать статический экземпляр io.quarkus.redis.datasource.RedisDataSource;

RedisDataSource ds = new RedisDataSource();

Выдает ошибку компилятора «Невозможно создать экземпляр типа RedisDataSource».

Я попытался создать экземпляр Singleton следующим образом:

@Singleton
public class RedisDataSourceSingleton {
  private static RedisDataSource instance;

  @Inject
  public RedisDataSourceSingleton(RedisDataSource ds) {
      instance = ds;
  }

  public static RedisDataSource getInstance() {
      if (instance == null) {
          throw new IllegalStateException("RedisDataSource is not initialized yet.");
      }
      return instance;
  }
}

но используя синглтон следующим образом:

import io.quarkus.redis.datasource.RedisDataSource;

// ...

  @BeforeAll
  static void resetTestData() {
    RedisDataSource ds = RedisDataSourceSingleton.getInstance();
    // use ds here
  }

бросает: «Метод getInstance() из типа RedisDataSourceSingleton ссылается на отсутствующий тип RedisDataSource»

Есть ли способ получить статический экземпляр io.quarkus.redis.datasource.RedisDataSource, чтобы я мог использовать его методы в своем методе @BeforeAll? Наверное, я слишком много думаю!

Спасибо, Мюррей

Похоже, это официальный способ сделать «BeforeAll»: quarkus.io/guides/… но, похоже, он работает не так, как ожидалось: github.com/quarkusio/quarkus/issues/35692

Murrah 08.06.2024 11:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
1
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы не можете получить доступ к управляемому компоненту (например, к источнику данных) из @BeforeAll. Вместо этого я бы использовал @Before или собственный QuarkusTestResourceLifecycleManager,, который запускался бы перед всеми тестами.

Другое решение, если вам нужен @BeforeAll, — создать экземпляр низкоуровневого клиента Redis (например, клиента Redis Vert.x, используемого в Quarkus) и выполнить команды.

Спасибо, Клемент. Извините за медленный ответ. Я изучал варианты. Как вы предлагаете, я почти нашел решение с помощью Vert.x. Я просто нажимаю io.vertx.core.http.ConnectionPoolTooBusyException и работаю над тем, как увеличить poolOptions.setMaxWaitQueueSize со значения по умолчанию, равного 24, или какое-то другое решение этой ошибки. Я опубликую свое решение, как только буду уверен, что оно работает.

Murrah 23.06.2024 10:52

В Quarkus вы можете настроить quarkus.redis.max-pool-waiting (см. quarkus.io/guides/redis-reference#redis-configuration-refere‌​nce)

Clement 24.06.2024 15:54
Ответ принят как подходящий

Хорошо. Следуя комментарию Клемента выше, я нашел способ сделать то, что мне нужно, с помощью Vert.x.

Еще раз сформулируем проблему: у меня есть набор тестовых классов, каждый из которых имеет несколько тестовых методов, которые выполняются последовательно (вспомните варианты CRUD). Мне нужно сбросить график FalkorDB для каждого класса перед запуском тестов в этом классе.

Аннотация @BeforeAll выполняется перед всеми тестами в классе. Однако это должен быть метод static, и сброс графика должен завершиться до начала любых тестов в этом классе.

«Подвох» заключается в том, что средство запуска тестов Quarkus (JUnit?) создает новый экземпляр класса для каждой тестовой функции в этом классе. Это означает, что использование конструктора класса в качестве способа инициализации тестовых данных в моем случае не вариант, поскольку результат одного теста передается в следующий тест в этом классе. В тестах используются аннотации @TestMethodOrder(OrderAnnotation.class) и @Test@Order(1) ... для обеспечения последовательности.

Мой первоначальный план для BeforeAll состоял в том, чтобы удалить ключ графа, а затем запустить последовательность запросов Cypher для воссоздания данных. Однако выполнение этого статическим способом стало проблематичным для очереди HTTP, и повторное выполнение известного набора запросов Cypher неэффективно. Что еще более важно, я понял, что могу использовать команду FalkorDB Graph Copy, чтобы скопировать ранее созданный график в нужное мне исходное состояние.

Я создал класс FalkorGraphSetup:

package com.flowt.falkordb.vertx;

import io.quarkus.logging.Log;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.redis.client.Redis;
import io.vertx.redis.client.RedisOptions;
import io.vertx.redis.client.Request;
import io.vertx.redis.client.Command;
import io.vertx.redis.client.Response;
import io.vertx.redis.client.impl.CommandImpl;
import io.vertx.redis.client.impl.KeyLocator;
import io.vertx.redis.client.impl.keys.BeginSearchIndex;
import io.vertx.redis.client.impl.keys.FindKeysRange;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.eclipse.microprofile.config.ConfigProvider;

/**
 * Setup the FalkorDB graph.
 *
 * This class uses static requests that return void
 * and are used solely to manipulate the FalkorDB graph.
 *
 * Its main (only?) purpose is to provide a way to reset the test data
 * because the JUnit @BeforeAll annotation must be a STATIC method.
 *
 * We use a CountDownLatch so the methods will return only after the graph request returns.
 * This effectively makes the methods synchronous, which we need
 * so we know the graph data has been initialised before the tests run.
 */
public class FalkorGraphSetup {
  private static Redis redisClient;

  // FYI: Define the custom commands that are in FalkorDB but not in the vertx client
  // ToDO: Move these to a separate class
  static Command GRAPH_COPY = new CommandImpl("graph.COPY", -1, false, false, false, new KeyLocator(true, new BeginSearchIndex(1), new FindKeysRange(0, 1, 0)), new KeyLocator(false, new BeginSearchIndex(2), new FindKeysRange(0, 1, 0)));

  // Use a static block so this section only runs once with the result
  // that we have a static instance of the redisClient to use with the methods below.
  static {
    Vertx vertx = Vertx.vertx();
    String connectionString = ConfigProvider.getConfig().getValue("quarkus.falkordb.hosts", String.class);
    RedisOptions redisOptions = new RedisOptions().setConnectionString(connectionString);
    redisClient = Redis.createClient(vertx, redisOptions);
  }

  /**
   * Delete the graph by key.
   * @param key
   */
  public static void graphDelete(String key) {
    Log.info("FalkorGraphSetup.graphDelete key: " + key);

    // Make sure key is not null or empty
    if (key == null || key.isEmpty()) {
      Log.error("FalkorGraphSetup.graphDelete key is null or empty");
      return;
    }

    // Create a CountDownLatch with a count of 1
    CountDownLatch latch = new CountDownLatch(1);

    // Execute the delete command
    redisClient
        .send(Request.cmd(Command.DEL).arg(key))
        .onComplete(ar -> {
          if (ar.succeeded()) {
              Response response = ar.result();
              Log.info("graphDelete operation on key '" + key + "' succeeded with response: " + response);
          } else {
              Throwable cause = ar.cause();
              Log.error("graphDelete operation failed with cause: " + cause);
          }

          // Decrement the count of the latch, allowing the waiting thread to proceed
          latch.countDown();
        });

    try {
        // Wait for the asynchronous operation to complete
        latch.await();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        Log.error("graphCopy was interrupted while waiting for completion");
    }
  }


  /**
   * FalkorDB only.
   * Copy a graph from src to dest using the custom GRAPH_COPY command defined above.
   * @param src
   * @param dest
   */
  public static void graphCopy(String src, String dest) {
    Log.info("FalkorGraphSetup.graphCopy src: " + src + " dest: " + dest);

    // Create a CountDownLatch with a count of 1
    CountDownLatch latch = new CountDownLatch(1);

    redisClient
        .send(Request.cmd(GRAPH_COPY).arg(src).arg(dest))
        .onComplete(ar -> {
          if (ar.succeeded()) {
              Response response = ar.result();
              Log.info("graphCopy operation from src: " + src + " to dest: " + dest + " succeeded with response: " + response);
          } else {
              Throwable cause = ar.cause();
              Log.error("graphCopy operation failed with cause: " + cause);
          }

          // Decrement the count of the latch, allowing the waiting thread to proceed
          latch.countDown();
        });

    try {
        // Wait for the asynchronous operation to complete
        latch.await();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        Log.error("graphCopy was interrupted while waiting for completion");
    }
  }

  /**
   * Execute a graph query on the specified key.
   * @param key
   * @param cmdStr
   */
  public static void graphQuery(String key, String cmdStr) {

    Log.info("FalkorGraphSetup.graphQuery key: " + key + " query: " + cmdStr);

    // Make sure key is not null or empty
    if (key == null || key.isEmpty()) {
      Log.error("FalkorGraphSetup.graphQuery key is null or empty");
      return;
    }

    // Make sure cmdStr is not null or empty
    if (cmdStr == null || cmdStr.isEmpty()) {
      Log.error("FalkorGraphSetup.graphQuery cmdStr is null or empty");
      return;
    }

    // Create a CountDownLatch with a count of 1
    CountDownLatch latch = new CountDownLatch(1);

    redisClient
        .send(Request.cmd(Command.GRAPH_QUERY).arg(key).arg(cmdStr))
        .onComplete(ar -> {
          if (ar.succeeded()) {
              Response response = ar.result();
              Log.info("Graph operation succeeded with response: " + response);
          } else {
              Throwable cause = ar.cause();
              Log.error("Graph operation failed with cause: " + cause);
          }

          // Decrement the count of the latch, allowing the waiting thread to proceed
          latch.countDown();
        });

    try {
        // Wait for the asynchronous operation to complete
        latch.await();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        Log.error("graphQuery was interrupted while waiting for completion");
    }
  }
}

Итак, когда приложение Quarkus запускается, у меня есть класс, который использует обычный клиент Quarkus Redis для создания набора графиков в FalkorDB, каждый из которых имеет суффикс «_is» (начальное состояние). например MyGraph1_is. Ничего особенного там нет.

Затем в своем тестовом классе BeforeAll я удаляю график MyGraph1 и воссоздаю его, копируя график MyGraph1_is в MyGraph1. например:

  @BeforeAll
  static void beforeAll() {
    Log.info("\n\n================ BEFORE ALL ================ = ");

    // Delete the graph by key
    FalkorGraphSetup.graphDelete("MyGraph1);

    // Make a fresh copy
    FalkorGraphSetup.graphCopy("MyGraph1_is","MyGraph1);

    Log.info("\n================ BEFORE ALL END =================\n");
  }

Максимальный тестовый граф имеет около 50 узлов и ребер. У меня около 20 тестовых классов. В этом масштабе все работает очень хорошо. Я еще не пробовал это с гигантскими графиками.

Любые предложения по улучшению будут очень приветствоваться.

В конечном итоге я собираюсь создать адаптивный клиент Quarkus FalkorDB с использованием Vert.x, созданного по образцу существующего клиента Quarkus Redis, но включающего только команды FalkorDB.

Ваше здоровье, Мюррей

Другие вопросы по теме