У меня есть набор тестовых классов в 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.
Вместо этого я бы использовал @Before
или собственный QuarkusTestResourceLifecycleManager,
, который запускался бы перед всеми тестами.
Другое решение, если вам нужен @BeforeAll, — создать экземпляр низкоуровневого клиента Redis (например, клиента Redis Vert.x, используемого в Quarkus) и выполнить команды.
Спасибо, Клемент. Извините за медленный ответ. Я изучал варианты. Как вы предлагаете, я почти нашел решение с помощью Vert.x. Я просто нажимаю io.vertx.core.http.ConnectionPoolTooBusyException и работаю над тем, как увеличить poolOptions.setMaxWaitQueueSize
со значения по умолчанию, равного 24, или какое-то другое решение этой ошибки. Я опубликую свое решение, как только буду уверен, что оно работает.
В Quarkus вы можете настроить quarkus.redis.max-pool-waiting
(см. quarkus.io/guides/redis-reference#redis-configuration-reference)
Хорошо. Следуя комментарию Клемента выше, я нашел способ сделать то, что мне нужно, с помощью 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.
Ваше здоровье, Мюррей
Похоже, это официальный способ сделать «BeforeAll»: quarkus.io/guides/… но, похоже, он работает не так, как ожидалось: github.com/quarkusio/quarkus/issues/35692