6.7.2. Интеграционные тесты Middleware
На уровне Middleware можно создавать интеграционные тесты, которые выполняются в полнофункциональном контейнере Spring с подключением к базе данных. В тестах такого типа можно выполнять код любого слоя внутри Middleware - от сервисов до ORM.
Для того, чтобы выполнять тесты из IDE, создайте каталог test
в модуле core рядом с src
. После этого пересоздайте проектные файлы IDE.
Платформа содержит класс TestContainer
, который может быть использован в качестве базового для тестовых контейнеров приложения. Создайте наследника этого класса в каталоге test
модуля core и в его конструкторе переопределите параметры загрузки компонентов и свойств приложения, а также параметры подключения к тестовой БД. Например:
public class SalesTestContainer extends TestContainer {
public SalesTestContainer() {
super();
appComponents = Arrays.asList(
"com.haulmont.cuba"
// add CUBA premium add-ons here
// "com.haulmont.bpm",
// "com.haulmont.charts",
// "com.haulmont.fts",
// "com.haulmont.reports",
// and custom app components if any
);
appPropertiesFiles = Arrays.asList(
// List the files defined in your web.xml
// in appPropertiesConfig context parameter of the core module
"com/company/sales/app.properties",
// Add this file which is located in CUBA and defines some properties
// specifically for test environment. You can replace it with your own
// or add another one in the end.
"test-app.properties",
"com/company/sales/test-app.properties");
dbDriver = "org.postgresql.Driver";
dbUrl = "jdbc:postgresql://localhost/sales_test";
dbUser = "cuba";
dbPassword = "cuba";
}
}
Пример собственного файлв test-app.properties
:
cuba.webContextName = app-core
sales.someProperty = someValue
В качестве базы данных рекомендуется использовать отдельную тестовую БД, которую можно создавать, например, следующей задачей в build.gradle
:
configure(coreModule) {
...
task createTestDb(dependsOn: assemble, description: 'Creates local Postgres database for tests', type: CubaDbCreation) {
dbms = 'postgres'
dbName = 'sales_test'
dbUser = 'cuba'
dbPassword = 'cuba'
}
Тестовый контейнер используется в классах тестов в качестве JUnit rule, указанного с помощью аннотации @ClassRule
:
public class CustomerLoadTest {
@ClassRule
public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
private Customer customer;
@Before
public void setUp() throws Exception {
customer = cont.persistence().createTransaction().execute(em -> {
Customer customer = cont.metadata().create(Customer.class);
customer.setName("testCustomer");
em.persist(customer);
return customer;
});
}
@After
public void tearDown() throws Exception {
cont.deleteRecord(customer);
}
@Test
public void test() {
try (Transaction tx = cont.persistence().createTransaction()) {
EntityManager em = cont.persistence().getEntityManager();
TypedQuery<Customer> query = em.createQuery(
"select c from sales$Customer c", Customer.class);
List<Customer> list = query.getResultList();
tx.commit();
assertTrue(list.size() > 0);
}
}
}
В данном примере тестовый контейнер инициализируется один раз для всех тестовых методов класса, и уничтожается после того, как все они выполнены.
Так как запуск контейнера занимает некоторое время, имеет смысл инициализировать контейнер один раз для тестов из нескольких (или всех) классов. Для этого создайте общий синглтон-экземпляр тестового контейнера, например:
public class SalesTestContainer extends TestContainer {
public SalesTestContainer() {
...
}
public static class Common extends SalesTestContainer {
public static final SalesTestContainer.Common INSTANCE = new SalesTestContainer.Common();
private static volatile boolean initialized;
private Common() {
}
@Override
public void before() throws Throwable {
if (!initialized) {
super.before();
initialized = true;
}
setupContext();
}
@Override
public void after() {
cleanupContext();
// never stops - do not call super
}
}
}
И используйте его в тестовых классах:
public class CustomerLoadTest {
@ClassRule
public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
...
}
- Полезные методы тестового контейнера
-
Класс
TestContainer
содержит следующие методы, которые можно использовать в коде тестов (см. примерCustomerLoadTest
выше):-
persistence()
- возвращает ссылку на интерфейсPersistence
. -
metadata()
- возвращает ссылку на интерфейсMetadata
. -
deleteRecord()
- этот набор перегруженных методов предназначен для использования в@After
методах для удаления тестовых объектов из БД.
-
- Логирование
-
Класс
TestContainer
настраивает логирование в соответствие с файломtest-logback.xml
, предоставляемым платформой. Данный файл содержится в артефактеcuba-core-tests
.Для того, чтобы настроить уровни логирования в своих тестах, необходимо выполнить следующее:
-
Скопируйте файл
test-logback.xml
из артефакта платформы в корень каталогаtest
модуляcore
проекта, например какmy-test-logback.xml
. -
Сконфигурируйте параметры логирования в
my-test-logback.xml
. -
Добавьте блок статической инициализации в класс тестового контейнера проекта и укажите местоположение файла конфигурации Logback в системном свойстве
logback.configurationFile
:public class MyTestContainer extends TestContainer { static { System.setProperty("logback.configurationFile", "my-test-logback.xml"); } // ... }
-
- Дополнительные хранилища
-
Если в вашем проекте используются дополнительные хранилища, необходимо создать соответствующие источники данных JDBC в тестовом контейнере. Например, если у вас есть хранилище
mydb
, являющееся базой данных PostgreSQL, добавьте следующий метод в класс тестового контейнера:public class MyTestContainer extends TestContainer { // ... @Override protected void initDataSources() { super.initDataSources(); try { Class.forName("org.postgresql.Driver"); TestDataSource mydbDataSource = new TestDataSource("jdbc:postgresql://localhost/mydatabase", "cuba", "cuba"); TestContext.getInstance().bind(AppContext.getProperty("cuba.dataSourceJndiName_mydb"), mydbDataSource); } catch (ClassNotFoundException | NamingException e) { throw new RuntimeException("Error initializing datasource", e); } } // ... }