В разделе приведены рекомендации по настройке и запуску интеграционных тестов в проектах THESIS Studio. Используйте их как практическое руководство при разработке, проверке и сопровождении проекта.
Полезные материалы:
Общее описание
Границы применения
Интеграционные тесты выполняются в контейнере Spring и позволяют проверять большую часть логики приложения, включая работу с базой данных и экранами пользовательского интерфейса.
Интеграционные тесты слоя core обычно используются для проверки компонентов, сервисов и логики, связанной с базой данных. При необходимости для них можно настроить отдельную тестовую базу данных с подготовленными данными.
Интеграционные тесты слоя web выполняются в контейнере Spring модуля web, который не связан напрямую с контейнером core. Поэтому для сервисов создаются заглушки. Такие тесты позволяют проверять логику бинов web-слоя, экранов и их компонентов: инициализацию, жизненный цикл, обработчики событий, источники данных и действия.
Особенности интеграционных тестов в ТЕЗИС
При создании интеграционных тестов в проектах расширения ТЕЗИС используются механизмы CUBA. Основное отличие заключается в необходимости адаптировать тестовую инфраструктуру под особенности ТЕЗИС.
Для тестов слоя core дополнительные действия, как правило, не требуются: тестовый контейнер создается THESIS Studio с учетом версии ТЕЗИС.
Для тестов слоя web необходимо переопределить часть методов TestUiEnvironment и добавить заглушки сервисов, которые используются при создании виртуального меню и главного окна.
Изменения в ТЕЗИС
ТЕЗИС 5.1.2
В версии ТЕЗИС 5.1.2 добавлен тестовый контейнер для интеграционных тестов слоя core:
com.haulmont.thesis.testsupport.ThesisTestContainer
Он предназначен для уменьшения объема типового кода в проектах расширения. Для ТЕЗИС 5.1.2 и выше тестовый контейнер проекта создается как наследник ThesisTestContainer. Для более ранних версий THESIS Studio включает необходимый код непосредственно в создаваемый контейнер.
ТЕЗИС 5.1.3
В версии ТЕЗИС 5.1.3 добавлена инфраструктура для интеграционных тестов слоя web:
com.haulmont.thesis.testsupport.ThesisTestUiEnvironment
Также добавлены заглушки сервисов, без которых web-тесты не запускаются:
-
FoldersService; -
ThesisFoldersService; -
ThesisPersistenceManagerService; -
ThesisFtsService; -
LicensingService.
Для версий ТЕЗИС ниже 5.1.3 эти классы необходимо создать вручную, например скопировать из исходных кодов ТЕЗИС.
ТЕЗИС 5.2.0
В версии ТЕЗИС 5.2.0 внесены изменения для поддержки дополнительных источников данных.
Предварительные требования (Pre-requirements)
JUnit 5
При создании нового проекта THESIS Studio использует JUnit 5. Для создания и запуска тестов JUnit 5 необходимо настроить задачу test в файле build.gradle.
Пример настройки:
configure([globalModule, coreModule, guiModule, webModule, portalModule, mobileRestModule]) {
test {
useJUnitPlatform()
}
}
JMockit
Для создания заглушек сервисов web-слоя необходимо подключить JMockit.
В блок configure всех модулей добавьте зависимость JMockit, если она еще не указана. Если JMockit используется только для web-тестов, зависимость можно добавить только в конфигурацию модуля web.
dependencies {
testCompile('org.jmockit:jmockit:1.48')
}
В корневой части файла build.gradle, вне блоков конфигурации, добавьте класс JmockitAgent:
class JmockitAgent implements CommandLineArgumentProvider {
FileCollection classpath
JmockitAgent(FileCollection classpath) {
this.classpath = classpath
}
Iterable<String> asArguments() {
def path = classpath.find { it.name.contains("jmockit") }.absolutePath
["-javaagent:${path}"]
}
}
После этого в блоке configure добавьте JmockitAgent как поставщик аргументов JVM для задачи test. Если JMockit используется только для web-тестов, настройку можно выполнить только для модуля web.
configure([globalModule, coreModule, guiModule, webModule, portalModule, mobileRestModule]) {
//...
test {
//...
jvmArgumentProviders.add(new JmockitAgent(classpath))
}
}
Интеграционные тесты core-слоя
Создание тестового контейнера
Тестовый контейнер создает контекст приложения: загружает файлы свойств, подключает компоненты, создает AppContext и настраивает подключение к базе данных.
При создании нового проекта THESIS Studio автоматически создает тестовый контейнер в модуле core, а также пример тестового класса. Если контейнер отсутствует, его можно создать через дерево проекта:
Business Logic → Tests → New → Integration Test (Middleware).
После этого THESIS Studio предложит создать тестовый контейнер. Для продолжения нажмите Generate and Continue.
Сгенерируйте тестовый контейнер, нажав на кнопку Generate and Continue.
В результате, в модуле core в папке с исходниками для тестов test, будет создан класс TestingTestContainer. Его содержимое зависит от версии ТЕЗИС.
После этого откроется диалог с параметрами класса теста (Test Name и Testing library).
Настройка тестового контейнера
Начиная с версии ТЕЗИС 5.1.2 тестовый контейнер создается как наследник ThesisTestContainer. Это упрощает подготовку тестовых контейнеров в проектах расширения.
В конструкторе тестового контейнера указываются следующие данные:
-
используемые компоненты. Необходимо указать все компоненты, заданные в зависимостях
appComponentфайлаbuild.gradle. ИдентификаторыgroupIdкомпонентов добавляются в списокTestContainer#appComponents. Начиная с ТЕЗИС 5.1.2ThesisTestContainerсамостоятельно добавляет компонентcom.haulmont.thesis.app, поэтому дополнительно требуется указать только пользовательские компоненты; -
используемые файлы свойств. Необходимо указать файлы, перечисленные в
web.xml, кроме файлов из каталогаconf, а также файл свойств для тестового окружения, напримерtest-app.properties. Порядок указания файлов должен соответствовать порядку вweb.xml. Пути к файлам добавляются в списокTestContainer#appPropertiesFiles. Начиная с ТЕЗИС 5.1.2ThesisTestContainerсамостоятельно добавляет файлы свойств CUBA и ТЕЗИС, поэтому дополнительно требуется указать только файлы свойств проекта, файлы свойств пользовательских компонентов при необходимости, а также файл свойств, используемый в тестах.
Пример контейнера core для проекта расширения com.company.edu на основе ТЕЗИС 5.1.3 и JUnit 5
package com.company.edu;
import com.haulmont.thesis.testsupport.ThesisTestContainer;
import org.junit.jupiter.api.extension.ExtensionContext;
public class EduTestContainer extends ThesisTestContainer {
public EduTestContainer() {
super();
// project and special properties for test environment.
appPropertiesFiles.add("edu-app.properties");
appPropertiesFiles.add("edu-test-app.properties");
autoConfigureDataSource();
}
public static class Common extends EduTestContainer {
// A common singleton instance of the test container which is initialized once for all tests
public static final EduTestContainer.Common INSTANCE = new EduTestContainer.Common();
private static volatile boolean initialized;
private Common() {}
@Override
public void beforeAll(ExtensionContext extensionContext) throws Exception {
if (!initialized) {
super.beforeAll(extensionContext);
initialized = true;
}
setupContext();
}
@SuppressWarnings("RedundantThrows")
@Override
public void afterAll(ExtensionContext extensionContext) throws Exception {
cleanupContext();
// never stops - do not call super
}
}
}
Дополнительные пояснения:
-
такой контейнер создается THESIS Studio при создании нового проекта;
-
EduTestContainer.Common.INSTANCEсодержит типовой код и позволяет не добавлять его в каждый тестовый класс; -
EduTestContainer.Common.INSTANCEявляется singleton-объектом, который по умолчанию используется во всех тестах. Благодаря этому контекст инициализируется только один раз — при запуске первого теста; -
в конструкторе добавляются только два файла свойств: файл свойств проекта и файл свойств тестового окружения. Остальные файлы добавляются в
ThesisTestContainer; -
вызов метода
autoConfigureDataSource()означает, что для подключения к базе данных используются учетные данные из файлов свойств при поставщике данныхapplicationили из файлаcontext.xmlпри поставщике данныхjndi.
Тестовый файл свойств
Тестовый файл свойств создается THESIS Studio при создании нового проекта и добавляется в список файлов свойств тестового контейнера. Файл используется для задания значений свойств, которые должны отличаться от основных настроек проекта при выполнении тестов.
По умолчанию файл содержит следующий набор свойств:
cuba.webHostName=localhost
cuba.webPort=8080
cuba.webContextName=app
thesis.hazelcast.port=5707
cuba.confDir=${user.dir}/build/test-run/${cuba.webContextName}/conf
cuba.logDir=${user.dir}/build/test-run/${cuba.webContextName}/logs
cuba.tempDir=${user.dir}/build/test-run/${cuba.webContextName}/temp
cuba.dataDir=${user.dir}/build/test-run/${cuba.webContextName}/work
thesis.resourcesDir=${user.dir}/web/WEB-INF/resources
thesis.processesSourcesDir=${user.dir}/src/process
#Cuba's test-spring.xml is important! It contains stubs to set up test behaviour
cuba.springContextConfig=+com/haulmont/cuba/testsupport/test-spring.xml \
edu-spring.xml \
edo-test-spring.xml
#cuba.trustedClientPermittedIpList=127.0.0.1, 10.13.*.*
#test DB connection params
cuba.dataSourceProvider = application
cuba.dataSource.username = cuba
cuba.dataSource.password = cuba
cuba.dataSource.dbName = edu_test
cuba.dataSource.host = localhost
cuba.dataSource.port = 5496
В файле задаются следующие группы параметров:
-
временные каталоги тестового запуска, включая
cuba.confDir,cuba.logDir,cuba.tempDirиcuba.dataDir; -
конфигурация Spring-контекста для тестов;
-
параметры подключения к тестовой базе данных.
В свойство cuba.springContextConfig добавляются не только конфигурационные файлы проекта, но и тестовая конфигурация CUBA. Она регистрирует специальные версии сервисов и бинов, предназначенные для тестирования. Также подключается тестовая конфигурация проекта, которая создается THESIS Studio при создании проекта.
В тестовой конфигурации проекта можно переопределять компоненты слоя core. Обычно это используется для следующих задач:
-
отключение отдельных функций, например обработка очереди сообщений в
ThesisTestEmailer; -
подстановка тестовой логики вместо рабочей, например
TestUserSessionSource#getUserSessionвсегда возвращает сессию пользователяtest_admin.
Параметры подключения к тестовой базе данных THESIS Studio заполняет автоматически на основе значений, указанных пользователем на втором шаге создания проекта.
При этом применяются следующие отличия:
-
cuba.dataSourceProviderвсегда устанавливается в значениеapplication; -
к имени базы данных добавляется суффикс
_test, напримерedu_test.
Пример тестовой реализации сервиса
При выполнении тестов может потребоваться замена отдельных сервисов или бинов тестовыми реализациями. Такой подход используется для отключения логики, не требующейся в тестовом окружении, а также для задания специального поведения, необходимого при проверке отдельного сценария.
Например, бин JodOpenOfficeIntegration, применяемый при просмотре документов, выполняет подключение к Office и может увеличивать время запуска контекста. Если при выполнении тестов этот компонент не используется, его можно переопределить и исключить действия, выполняемые при инициализации. Это позволяет сократить время подготовки тестового окружения.
Сначала необходимо создать наследника JodOpenOfficeIntegration в тестовых исходных файлах:
package com.company.edu.stubs;
import com.haulmont.thesis.preview.office.JodOpenOfficeIntegration;
public class JodOpenOfficeIntegrationTestStub extends JodOpenOfficeIntegration {
@Override
public void applicationStarted() { /*do nothing*/ }
@Override
public void afterPropertiesSet() throws Exception { /*do nothing*/ }
@Override
public void applicationStopped() { /*do nothing*/ }
@Override
protected synchronized void start() {}
@Override
protected synchronized void stop() {}
}
Затем созданный бин необходимо зарегистрировать в Spring-дескрипторе, используемом при выполнении тестов. В данном примере используется файл edu-test-spring.xml. Если файл отсутствует, его необходимо создать.
Пример содержимого файла:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- switch off integration -->
<bean id="df_OpenOfficeIntegration" class="com.company.edu.stubs.JodOpenOfficeIntegrationTestStub"/>
</beans>
Если указанный Spring-дескриптор уже добавлен в свойство cuba.springContextConfig тестового файла свойств, дополнительных действий не требуется.
Если файл не подключен, его необходимо добавить в cuba.springContextConfig. В значении свойства должны быть указаны Spring-конфигурация проекта, тестовая конфигурация CUBA и тестовая конфигурация проекта в порядке, описанном в предыдущем разделе.
Использование тестовой базы данных
При выполнении тестов часто используется отдельная тестовая база данных. Это позволяет не смешивать данные, создаваемые тестами, с данными, которые используются при обычном запуске системы.
В качестве примера рассматривается локальная база данных PostgreSQL, доступная на порту 5410, с пользователем cuba, которому предоставлены необходимые права.
Для настройки подключения к тестовой базе данных можно использовать несколько способов.
Использование подхода по умолчанию
Этот способ применяется THESIS Studio при создании нового проекта.
Независимо от того, какой поставщик источника данных используется в проекте, в тестовом файле свойств указывается значение application и задаются учетные данные для подключения к базе данных.
В этом случае для инициализации параметров подключения в конструкторе тестового контейнера используется метод:
autoConfigureDataSource()
Использование system properties
Параметры подключения можно передать через system properties.
Пример:
test.remoteDb=true
test.remoteDb.dbms=mssql
test.remoteDb.host=10.0.1.1
test.remoteDb.name=edu_test
test.remoteDb.user=sa
test.remoteDb.pass=SA_assword_5!
Важно!
При использованииsystem propertiesнеобходимо удалить вызов методаautoConfigureDataSource()из конструктора тестового контейнера.
Переопределение метода setDefaultDbParams()
Если указанные выше способы не подходят, параметры подключения к тестовой базе данных можно задать путем переопределения метода `setDefaultDbParams()` в тестовом контейнере.
Пример:
@Override
protected void setDefaultDbParams() {
dbDriver = "oracle.jdbc.OracleDriver";
dbUrl = "jdbc:oracle:thin:@//localhost/edu_test";
dbUser = "root";
dbPassword = "root";
}
Важно!
При переопределении методаsetDefaultDbParams()необходимо удалить вызов методаautoConfigureDataSource()из конструктора тестового контейнера.
Настройка подключения для отдельного теста
Если параметры подключения нужно задать только для конкретного теста, вместо Common.INSTANCE можно явно создать экземпляр тестового контейнера.
Пример:
public class SomeTest {
@RegisterExtension
public EduTestContainer container = new EduTestContainer()
.setDbDriver("net.sourceforge.jtds.jdbc.Driver")
.setDbUrl("jdbc:jtds:sqlserver://localhost:32012/edu_test")
.setDbUser("sa")
.setDbPassword("SA_assword_5!");
}
Важно!
При таком подходе для теста будет заново создаваться контекст приложения, что увеличивает время выполнения тестов.
Создание тестовой БД
Для удобства создания тестовой базы данных THESIS Studio при создании нового проекта автоматически добавляет в файл build.gradle задачу createTestDb.
Если задача отсутствует, ее необходимо создать вручную.
Пример:
task createTestDb(dependsOn: assembleDbScripts, description: 'Creates test database', type: CubaDbCreation) {
dbms = 'postgres'
host = 'localhost:5496'
dbName = 'edu_test'
dbUser = 'cuba'
dbPassword = 'cuba'
}
Несмотря на то что параметры подключения к тестовой базе данных указаны в файле свойств, для задачи создания тестовой базы данных необходимо явно указать параметры соединения. Иначе задача будет использовать параметры из основного файла свойств проекта, например edu-app.properties, а не из тестового файла edu-test-app.properties.
При выполнении тестов в базе данных обычно создаются или изменяются записи. Поэтому может потребоваться создавать тестовую базу данных перед каждым запуском тестов.
Для этого укажите зависимость задачи test от задачи createTestDb:
test.dependsOn createTestDb
Класс теста
Для создания теста используйте дерево проекта **Business Logic → Tests**. Щелкните правой кнопкой мыши по этой ветви и в контекстном меню выберите пункт **New → Integration Test (Middleware)**.
Если тестовый контейнер создан, откроется диалоговое окно создания тестового класса. В нем можно указать:
- имя класса теста;
- пакет. Значение по умолчанию — `{$rootPackage}.core`;
- контейнер. В проекте может быть несколько контейнеров с разными параметрами и файлами свойств. По умолчанию отображается стандартный контейнер `Common`;
- версию JUnit. JUnit 5 доступен только в том случае, если задача `test` в конфигурации всех модулей настроена с использованием `useJUnitPlatform()`.
Пример класса теста для проекта расширения edu, на основе ТЕЗИС 5.1.3, JUnit 5
package com.company.edu.core;
import com.company.edu.EduTestContainer;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.thesis.testsupport.ThesisTestContainer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.*;
class MyIntegrationTest {
@RegisterExtension
static ThesisTestContainer container = EduTestContainer.Common.INSTANCE;
static DataManager dataManager;
@BeforeAll
static void beforeAll() {
dataManager = AppBeans.get(DataManager.class);
}
@Test
void myTestMethod() {
}
}
Пример класса теста для прокета расширения edu, на основе ТЕЗИС 5.1.3, JUnit 4
package com.company.edu.core;
import com.company.edu.EduTestContainer;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.thesis.testsupport.ThesisTestContainer;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import static org.junit.Assert.*;
// See https://doc.cuba-platform.com/manual-7.2/integration_tests_mw.html
public class MyIntegrationTest_1 {
@ClassRule
public static ThesisTestContainer container = EduTestContainer.Common.INSTANCE;
private DataManager dataManager;
@Before
public void setUp() throws Exception {
dataManager = AppBeans.get(DataManager.class);
}
@Test
public void myTestMethod() {
}
}
Используемые аннотации и методы жизненного цикла зависят от версии JUnit.
В классе теста используются следующие элементы:
-
регистрация ресурса, отвечающего за установку контекста выполнения теста. Для JUnit 5 используется аннотация
org.junit.jupiter.api.extension.RegisterExtension, для JUnit 4 —org.junit.ClassRule; -
метод инициализации теста. В нем можно подготовить атрибуты класса, данные в базе данных и другие сведения, необходимые для выполнения теста;
-
тестовый метод, содержащий код проверки.
Для JUnit 5 метод инициализации может быть статическим методом beforeAll(), отмеченным аннотацией org.junit.jupiter.api.BeforeAll.
Для JUnit 4 метод инициализации обычно задается методом setUp(), отмеченным аннотацией org.junit.Before. В отличие от BeforeAll, такой метод выполняется перед каждым тестовым методом.
Тестовый метод определяется аннотацией:
-
org.junit.jupiter.api.Test— для JUnit 5; -
org.junit.Test— для JUnit 4.
Один класс может содержать несколько тестовых методов. Они выполняются последовательно.
Если после выполнения тестов требуется очистить данные, можно создать отдельный метод и отметить его аннотацией org.junit.jupiter.api.AfterAll. Если код должен выполняться перед каждым тестом или после каждого теста, используются аннотации org.junit.jupiter.api.BeforeEach и org.junit.jupiter.api.AfterEach. Для JUnit 4 применяются аннотации org.junit.Before и org.junit.After.
В JUnit 5 по умолчанию экземпляр тестового класса создается для каждого тестового метода. Поэтому нестатические атрибуты не имеют общего состояния между тестовыми методами.
Если состояние атрибута должно быть общим для нескольких тестовых методов, можно использовать один из следующих подходов:
-
объявить атрибут статическим. Этот подход используется по умолчанию. Поэтому в примере для JUnit 5 атрибуты
containerиdataManager, а также методы с аннотациями@BeforeAllи@AfterAll, являются статическими; -
добавить к тестовому классу аннотацию
@TestInstance(TestInstance.Lifecycle.PER_CLASS)из пакетаorg.junit.jupiter.api. В этом случае один экземпляр тестового класса используется для всех тестовых методов, и состояние нестатических атрибутов становится общим.
Важно!
Не следует смешивать аннотации JUnit 4 и JUnit 5 в одном тестовом классе. Например, необходимо использовать аннотациюTestтолько из той версии JUnit, которая применяется в данном тесте. Смешивание аннотаций разных версий может привести к ошибкам выполнения тестов.:
Интеграционные тесты web-слоя
Тестовый контейнер
Класс тестового контейнера web-модуля аналогичен тестовому контейнеру core-модуля.
Он имеет следующие особенности:
-
поддерживает работу с JUnit 4 и JUnit 5;
-
создает контекст выполнения тестов, включая загрузку файлов свойств, подключение компонентов и создание `AppContext`;
-
создается THESIS Studio автоматически при создании нового проекта.
-
Если тестовый контейнер web-модуля отсутствует, его необходимо создать по аналогии с тестовым контейнером core-модуля.
Единственное отличие заключается в том, что при создании теста необходимо выбрать пункт **New → Integration Test (Web)**.
Настройка тестового контейнера
Для контейнера web-слоя в ТЕЗИС не предусмотрена базовая версия контейнера, поэтому он наследуется от com.haulmont.cuba.web.testsupport.TestContainer.
В конструкторе тестового контейнера задаются следующие параметры.
Используемые компоненты. Необходимо указать все компоненты, заданные в зависимостях appComponent файла build.gradle. Идентификаторы GroupId добавляются в список TestContainer#appComponents. В список включается компонент com.haulmont.thesis.app, а также пользовательские компоненты, подключенные в проекте.
Используемые файлы свойств. Необходимо указать файлы, перечисленные в web.xml, за исключением файлов, расположенных в каталоге conf, а также файл, задающий значения отдельных свойств для тестового окружения, например test-web-app.properties.
Последовательность указания файлов должна соответствовать порядку в web.xml. Пути к файлам добавляются в список TestContainer#appPropertiesFiles.
В список необходимо включить:
- файлы свойств CUBA и ТЕЗИС;
- файлы свойств проекта;
- файлы свойств подключенных пользовательских компонентов, если их дескриптор не содержит все значения свойств, описанные в файлах свойств;
- файл свойств, используемый при выполнении тестов.
Пример контейнера web для прокета расширения com.company.edu, на основе ТЕЗИС 5.1.3, JUnit 5
package com.company.edu;
import com.google.common.collect.Lists;
import com.haulmont.cuba.web.testsupport.TestContainer;
public class EduWebTestContainer extends TestContainer {
public EduWebTestContainer() {
appComponents = Lists.newArrayList("com.haulmont.thesis.app");
appPropertiesFiles = Lists.newArrayList(
// List the files defined in your web.xml
// in appPropertiesConfig context parameter of the core module
// Add properties file used in test environment in the end of the list
"com/haulmont/cuba/web-app.properties",
"thesis-web-app.properties",
"edu-web-app.properties",
"edu-test-web-app.properties"
);
springConfig="";
}
public static class Common extends EduWebTestContainer {
// A common singleton instance of the test container which is initialized once for all tests
public static final EduWebTestContainer.Common INSTANCE = new EduWebTestContainer.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
}
}
}
Примечание
Такой контейнер создается THESIS Studio при создании нового проекта.
EduTestContainer.Common.INSTANCE содержит типовой код, необходимый для инициализации тестового окружения. Чтобы не добавлять этот код в каждый тест, используется общий экземпляр контейнера.
Дополнительно EduTestContainer.Common.INSTANCE является синглтоном и по умолчанию используется во всех тестах. Благодаря этому контекст инициализируется только один раз — при запуске первого теста.
Как и для core-модуля, в контейнере указываются подключаемые компоненты и файлы свойств. В данном случае используются файлы свойств web-модуля.
Тестовый файл свойств
Тестовый файл свойств создается THESIS Studio при создании нового проекта и добавляется в список файлов свойств тестового контейнера. Он используется для задания значений, отличающихся от параметров основного проекта и применяемых только при выполнении тестов.
По умолчанию файл содержит набор свойств, аналогичный core-модулю, но без параметров подключения к базе данных:
cuba.confDir=${user.dir}/build/test-run/${cuba.webContextName}/conf
cuba.logDir=${user.dir}/build/test-run/${cuba.webContextName}/logs
cuba.tempDir=${user.dir}/build/test-run/${cuba.webContextName}/temp
cuba.dataDir=${user.dir}/build/test-run/${cuba.webContextName}/work
thesis.resourcesDir=${user.dir}/web/WEB-INF/resources
#Cuba's test-spring.xml is important! It contains stubs to set up test behaviour
cuba.springContextConfig=+com/haulmont/cuba/web/testsupport/test-web-spring.xml \
edu-web-spring.xml \
edu-test-web-spring.xml
Тестовая Spring-конфигурация test-web-spring.xml по умолчанию переопределяет клиентский UserSessionSource и использует com.haulmont.thesis.testsupport.ThesisTestUserSessionSource.
TestUiEnvironment
В интеграционных тестах web-модуля основную роль выполняет TestUiEnvironment. В отличие от тестового контейнера, именно этот класс используется непосредственно в тестах. При этом доступ к контейнеру при необходимости сохраняется.
TestUiEnvironment выполняет следующие действия:
- передает тестовому контейнеру инициализацию Spring-контекста;
- создает серверную сессию
UserSessionи клиентскую сессиюClientSession; - регистрирует заглушки базовых сервисов;
- создает UI Vaadin, включая
App,AppUI,VaadinSessionиVaadinRequest.
Для использования в ТЕЗИС и проектах расширения данный класс должен быть адаптирован. Соответствующая реализация добавлена начиная с ТЕЗИС 5.1.3:
com.haulmont.thesis.testsupport.ThesisTestUiEnvironment
Если используется версия ТЕЗИС ниже 5.1.3, необходимо самостоятельно создать такой класс и соответствующие заглушки сервисов, а затем использовать их в тестах. В качестве примера можно использовать пакет com.haulmont.thesis.testsupport web-модуля из исходного кода ТЕЗИС версии 5.1.3 или выше.
Заглушки сервисов
Так как реализация сервисов находится на уровне core, для использования в web-тестах необходимо создавать proxy-объекты, имитирующие работу соответствующих сервисов.
По умолчанию используется proxy-реализация, в которой методы не выполняют действий, а при необходимости вернуть значение возвращается null.
Если требуется изменить поведение сервиса в тесте, необходимо зарегистрировать собственную тестовую реализацию с помощью класса:
com.haulmont.cuba.web.testsupport.proxy.TestServiceProxy
Для регистрации используются статические методы:
<T> void mock(Class<T> serviceInterface, @Nullable T mock)
<T> void setDefault(Class<T> serviceInterface, @Nullable T defaultProxy)
Метод setDefault() задает реализацию по умолчанию, которая используется, если для сервиса не зарегистрирована отдельная тестовая реализация.
Возможны два варианта настройки.
Регистрация общей тестовой реализации сервиса
В этом случае необходимо создать тестовый класс, реализующий методы интерфейса сервиса, расширить TestUiEnvironment и зарегистрировать созданную реализацию в методе setupWebBeans().
Такой подход рекомендуется использовать, если тестовая реализация должна применяться во всех тестах.
Например, в ThesisTestUiEnvironment таким образом регистрируется тестовая реализация LicensingService:
TestServiceProxy.mock(LicensingService.class, new ThesisTestLicensingService());
Регистрация тестовой реализации на уровне класса теста
Тестовую реализацию можно зарегистрировать в конкретном тестовом классе, например в методе с аннотацией @Before или @BeforeAll.
В этом случае такая реализация будет использоваться только в пределах данного тестового класса.
Класс теста
Для создания web-теста используйте дерево проекта Business Logic → Tests.
Щелкните правой кнопкой мыши по ветви Tests и в контекстном меню выберите пункт New → Integration Test (Web).
Если тестовый контейнер уже создан, откроется диалоговое окно создания тестового класса. По составу полей оно аналогично диалоговому окну, используемому при создании core-тестов..
Ниже приведены примеры классов тестов для проекта расширения edu на основе ТЕЗИС 5.1.3:
- пример класса web-теста для JUnit 5;
package com.company.edu.web;
import com.company.edu.EduWebTestContainer;
import com.haulmont.cuba.web.testsupport.TestUiEnvironment;
import com.haulmont.thesis.testsupport.ThesisTestUiEnvironment;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.*;
class MyIntegrationTest_1 {
@RegisterExtension
TestUiEnvironment environment = new ThesisTestUiEnvironment(EduWebTestContainer.Common.INSTANCE).withUserLogin("admin");
@Test
void myTestMethod() {
}
}
- пример класса web-теста для JUnit 4.
package com.company.edu.web;
import com.company.edu.EduWebTestContainer;
import com.haulmont.cuba.web.testsupport.TestUiEnvironment;
import com.haulmont.thesis.testsupport.ThesisTestUiEnvironment;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.*;
public class MyIntegrationTest {
@Rule
public TestUiEnvironment environment = new ThesisTestUiEnvironment(EduWebTestContainer.Common.INSTANCE).withUserLogin("admin");
@Test
public void myTestMethod() {
}
}
Основное отличие web-теста от интеграционного теста слоя core заключается в регистрируемом ресурсе. В web-тесте регистрируется не тестовый контейнер, TestUiEnvironment, наследующий org.junit.rules.ExternalResource.
Экземпляр тестового контейнера передается в конструктор TestUiEnvironment.
Для входа в тестовое окружение используется метод withUserLogin(), в котором указывается имя пользователя.
Особенности тестирования UI на примере теста TaskEditor
Задача теста - проверить состояние вкладки Проекты: отображается она или скрыта при изменении типа задачи либо при заполнении задачи по шаблону.
Для сложных экранов, таких как карточки, перед выполнением проверки требуется подготовить тестовые данные и настроить необходимые зависимости.
Создание тестового контроллера TestTaskEditor
Применение типа задачи выполняется методом doChangeTaskType(). Метод имеет модификатор доступа protected, поэтому его нельзя вызвать напрямую из теста. Аналогичное ограничение есть у метода fillTask(TaskPattern taskPattern), который используется для заполнения задачи по шаблону.
Для выполнения теста необходимо сделать эти методы доступными. Для этого целесообразно создать тестовую версию контроллера TaskEditor и использовать ее в тестовом сценарии.
Кроме того, метод initCategoryAttrsFromCard(TsCard tsCard, boolean rewriteProps) выполняет логику, которая не используется в данном тесте. Чтобы не создавать дополнительные заглушки сервисов, в тестовом контроллере этот метод можно переопределить без выполнения действий.
Для этого в тестовых исходных файлах создаются дескриптор и контроллер.
com/company/edu/ui/task/test-task-edit.xml
<window
xmlns="http://schemas.haulmont.com/cuba/window.xsd"
xmlns:ext="http://schemas.haulmont.com/cuba/window-ext.xsd"
class="com.company.edu.ui.task.TestTaskEditor"
messagesPack="com.haulmont.thesis.web.ui.task"
focusComponent="taskName"
datasource="cardDs"
caption="msg://taskEditor.caption"
extends="com/haulmont/thesis/web/ui/task/task-edit.xml">
<layout/>
</window>
com.company.edu.ui.task.TestTaskEditor
package com.company.edu.ui.task;
import com.haulmont.thesis.core.entity.Task;
import com.haulmont.thesis.core.entity.TaskPattern;
import com.haulmont.thesis.core.entity.TsCard;
import com.haulmont.thesis.web.ui.task.TaskEditor;
/**
* Used in test to make method public (to invoke in test directly)
*/
public class TestTaskEditor extends TaskEditor<Task> {
@Override
public void fillTask(TaskPattern taskPattern) {
super.fillTask(taskPattern);
}
@Override
public void doChangeTaskType() {
super.doChangeTaskType();
}
@Override
protected void initCategoryAttrsFromCard(TsCard tsCard, boolean rewriteProps) {
// do nothing because we don't use additional category attributes in tests
}
}
После создания тестового контроллера необходимо зарегистрировать его в конфигурации экранов. Для этого в тестовых исходных файлах создается конфигурационный файл экранов.
edu-test-web-screens.xml
<screen-config xmlns="http://schemas.haulmont.com/cuba/screens.xsd">
<screen id="tm$Task.edit" template="com/company/edu/ui/task/test-task-edit.xml"/>
</screen-config>
Затем этот конфигурационный файл необходимо добавить в тестовый файл свойств.
edu-test-web-app.properties
cuba.windowConfig=+\
edu-web-screens.xml \
edu-test-web-screens.xml
cuba.web.urlHandlingMode = NONE
Создание тестовых сущностей
Для создания сущностей в тестах используется TestEntityFactory. Экземпляр фабрики предоставляет тестовый контейнер. Ссылку на контейнер можно получить через TestUiEnvironment.
TestEntityFactory содержит методы create(), с помощью которых можно создать сущность и задать значения ее полей.
Пример:
TestEntityFactory<TaskType> taskTypesFactory =
environment.getContainer().getEntityFactory(TaskType.class, TestEntityState.DETACHED);
TaskType invisibleProjectsType = typesFactory.create(
"name", "invisible Projects",
"code", "invisible Projects",
"entityType", "tm$Task",
"additionalFieldsTabName", "Доп. поля",
"fieldsXml", String.format(FIELDS_XML_PATTERN, false),
"createdBy", "admin"
);
Для выполнения теста необходимо создать следующие сущности:
-
задачу;
-
вид задачи;
-
шаблон задачи;
-
проект.
Пример создания видов и шаблонов задач, используемых при тестировании
TestEntityFactory<Task> tasksFactory;
TestEntityFactory<TaskType> typesFactory;
TestEntityFactory<TaskPattern> taskPatternsFactory;
TestEntityFactory<Project> projectsFactory;
Task task;
TaskPattern invisibleProjectsPattern;
TaskPattern visibleProjectsPattern0;
TaskPattern visibleProjectsPattern2;
TaskType invisibleProjectsType;
TaskType invisibleProjectsType1;
TaskType visibleProjectsType;
TaskType visibleProjectsType1;
@BeforeEach
void before() {
tasksFactory = environment.getContainer().getEntityFactory(Task.class, TestEntityState.NEW);
typesFactory = environment.getContainer().getEntityFactory(TaskType.class, TestEntityState.DETACHED);
taskPatternsFactory = environment.getContainer().getEntityFactory(TaskPattern.class, TestEntityState.DETACHED);
projectsFactory = environment.getContainer().getEntityFactory(Project.class, TestEntityState.DETACHED);
// init test entities
task = tasksFactory.create(Collections.emptyMap());
invisibleProjectsType = createType("invisible Projects", false);
invisibleProjectsType1 = createType("invisible Projects 1", false);
visibleProjectsType = createType("invisible Projects", true);
visibleProjectsType1 = createType("invisible Projects 1", true);
invisibleProjectsPattern = createTaskPattern("invisibleProjectsPattern", invisibleProjectsType, null);
visibleProjectsPattern0 = createTaskPattern("visibleProjectsPattern - 0", visibleProjectsType, null);
visibleProjectsPattern2 = createTaskPattern("visibleProjectsPattern - 2", visibleProjectsType, createProjects());
}
TaskType createType(String name, boolean visibleCardProjectsTab) {
return typesFactory.create(
"name", name,
"code", name,
"entityType", "tm$Task",
"additionalFieldsTabName", "Доп. поля",
"fieldsXml", String.format(FIELDS_XML_PATTERN, visibleCardProjectsTab),
"createdBy", "admin"
);
}
TaskPattern createTaskPattern(String patternName, TaskType taskType, Set<Project> projects) {
return taskPatternsFactory.create(
"patternName", patternName,
"taskName", "FreakingTask",
"taskType", taskType,
"projects", CollectionUtils.isEmpty(projects) ? Collections.emptySet() : projects,
"project", CollectionUtils.isNotEmpty(projects) ? projects.iterator().next() : null,
"createdBy", "admin"
);
}
Set<Project> createProjects() {
ProjectGroup projectGroup = environment.getContainer().getBean(Metadata.class).create(ProjectGroup.class);
projectGroup.setName("main group");
Set<Project> projects = new HashSet<>(2);
projects.add(projectsFactory.create("name", "1", "projectGroup", projectGroup));
projects.add(projectsFactory.create("name", "2", "projectGroup", projectGroup));
return projects;
}
Создание заглушек сервисов
При инициализации редактора задачи используется набор сервисов. Так как в тесте вызываются только отдельные методы этих сервисов, для них необходимо создать и зарегистрировать соответствующие заглушки. Для каждого метода указывается, какое значение должно возвращаться при его вызове.
Поведение метода задается с помощью Expectations, а регистрация заглушки выполняется через TestServiceProxy. Подробное описание механизма приведено в документации JMockit, в том числе в Javadoc для mockit.Expectations.
Общий порядок настройки:
-
Создайте поле с типом интерфейса сервиса.
-
Добавьте к полю аннотацию
@Mocked. -
В методе инициализации теста создайте ожидания: вызов метода сервиса и результат этого вызова.
-
Зарегистрируйте заглушку с помощью
TestServiceProxy.
Ниже приведены два распространенных варианта настройки.
Возврат фиксированного значения
При инициализации редактора вызывается метод ImageRecognitionService#getRecognitionEnabled(). Так как в данном тесте эта функциональность не проверяется, метод должен возвращать false.
Пример:
@Mocked
ImageRecognitionService imageRecognitionService;
@BeforeEach
void before() {
new Expectations() {{
imageRecognitionService.getRecognitionEnabled();
result = false;
}};
TestServiceProxy.mock(ImageRecognitionService.class, imageRecognitionService);
}
Возврат значения с учетом логики
Если возвращаемое значение зависит от входных параметров, в качестве результата используется объект mockit.Delegate. В нем описывается логика метода сервиса.
Например, при заполнении задачи по шаблону выполняется повторная загрузка сущности шаблона через DataService. В тесте вместо значения null, возвращаемого по умолчанию, необходимо вернуть одну из заранее подготовленных сущностей.
Пример:
TaskPattern invisibleProjectsPattern;
TaskPattern visibleProjectsPattern0;
TaskPattern visibleProjectsPattern2;
@Mocked
DataService dataService;
@BeforeEach
void before() {
new Expectations() {{
dataService.load(withInstanceOf(LoadContext.class));
result = new Delegate<DataService>() {
<E extends Entity> E load(LoadContext<E> context) {
if ("tm$TaskPattern".equals(context.getMetaClass())) {
if (invisibleProjectsPattern.getId().equals(context.getId())) {
return (E) invisibleProjectsPattern;
}
if (visibleProjectsPattern0.getId().equals(context.getId())) {
return (E) visibleProjectsPattern0;
}
if (visibleProjectsPattern2.getId().equals(context.getId())) {
return (E) visibleProjectsPattern2;
}
}
return null;
}
};
}};
TestServiceProxy.mock(DataService.class, dataService);
}
Тестирование контроллера
При тестировании экранов сначала необходимо создать экран в виртуальном пользовательском интерфейсе.
Для экранов CUBA 7 UI используется Screens, доступный через TestUiEnvironment#getScreens().
Для старого пользовательского интерфейса, к которому относится большинство экранов ТЕЗИС, используется WindowManager. Его можно получить через TestUiEnvironment#getUi()#getWindowManager() или через ThesisTestUiEnvironment#getWindowManager().
Пример открытия экрана:
WindowManager windowManager = environment.getWindowManager();
WindowConfig windowConfig = AppBeans.get(WindowConfig.NAME);
WindowInfo windowInfo = windowConfig.getWindowInfo("tm$Task.edit");
TestTaskEditor editor = (TestTaskEditor) windowManager.openEditor(
windowInfo,
task,
WindowManager.OpenType.NEW_TAB,
Collections.emptyMap()
);
После получения ссылки на контроллер можно вызывать его методы, а также получать компоненты экрана и изменять их состояние.
Пример получения компонентов и источника данных:
LookupField taskTypeLookup = (LookupField) editor.getWindow().getComponentNN("taskType");
TabSheet tabSheet = (TabSheet) editor.getWindow().getComponentNN("tabsheet");
CollectionDatasource<Project, UUID> cardProjectsDs =
(CollectionDatasource<Project, UUID>) editor.getDsContext().getNN("cardProjectsDs");
Set<Project> projects = createProjects();
Пример проверки:
cardProjectsDs.addItem(projects.iterator().next());
taskTypeLookup.setValue(invisibleProjectsType);
editor.doChangeTaskType();
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
Assertions.assertFalse(tabSheet.getTab("cardProjectsTab").isVisible());
Assertions.assertNull(editor.getCardTabSheetHelper().getHiddenTabs().get("cardProjectsTab"));
В данном примере в источник данных добавляется проект, в поле выбора устанавливается вид задачи, затем вызывается метод применения вида задачи. После этого проверяется наличие вкладки Проекты, ее видимость и отсутствие записи о скрытой вкладке во внутреннем состоянии контроллера.5.7.5 Полный код Теста
Проверка видимости вкладки Проекты для разных видов задач и при заполнении задачи по шаблону с разными видами задач и числом проектов.
Полный код теста TaskEdit для ТЕЗИС 5.1.3
package com.company.edu.web.task;
import com.company.edu.EduWebTestContainer;
import com.haulmont.cuba.core.app.DataService;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.gui.WindowManager;
import com.haulmont.cuba.gui.components.LookupField;
import com.haulmont.cuba.gui.components.TabSheet;
import com.haulmont.cuba.gui.config.WindowConfig;
import com.haulmont.cuba.gui.config.WindowInfo;
import com.haulmont.cuba.gui.data.CollectionDatasource;
import com.haulmont.cuba.web.testsupport.TestEntityFactory;
import com.haulmont.cuba.web.testsupport.TestEntityState;
import com.haulmont.cuba.web.testsupport.TestUiEnvironment;
import com.haulmont.cuba.web.testsupport.proxy.TestServiceProxy;
import com.haulmont.thesis.core.app.*;
import com.haulmont.thesis.core.entity.*;
import com.haulmont.thesis.core.entity.multi_language.MultiLanguageString;
import com.haulmont.thesis.core.legaldocintegration.LegalDocIntegrationService;
import com.haulmont.thesis.core.process.TaskManagementConstants;
import com.haulmont.thesis.testsupport.ThesisTestUiEnvironment;
import com.haulmont.workflow.core.app.WfService;
import com.haulmont.workflow.core.entity.Card;
import com.haulmont.workflow.core.entity.Proc;
import mockit.Delegate;
import mockit.Expectations;
import mockit.Mocked;
import org.apache.commons.collections4.CollectionUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@SuppressWarnings({"rawtypes", "unchecked"})
public class HiddenTabsTest {
@RegisterExtension
TestUiEnvironment environment = new ThesisTestUiEnvironment(EduWebTestContainer.Common.INSTANCE).withUserLogin("test_admin");
Task task;
TestEntityFactory<Task> tasksFactory;
TestEntityFactory<TaskType> typesFactory;
TestEntityFactory<TaskPattern> taskPatternsFactory;
TestEntityFactory<Project> projectsFactory;
TaskPattern invisibleProjectsPattern;
TaskPattern visibleProjectsPattern0;
TaskPattern visibleProjectsPattern2;
TaskType invisibleProjectsType;
TaskType invisibleProjectsType1;
TaskType visibleProjectsType;
TaskType visibleProjectsType1;
@Mocked
ImageRecognitionService imageRecognitionService;
@Mocked
TaskmanService taskmanService;
@Mocked
WfService wfService;
@Mocked
LegalDocIntegrationService legalDocIntegrationService;
@Mocked
UnsecuredDataService unsecuredDataService;
@Mocked
SignatureService signatureService;
@Mocked
DocService docService;
@Mocked
ThesisReportService thesisReportService;
@Mocked
DataService dataService;
static final String FIELDS_XML_PATTERN = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"\n" +
"<fields>\n" +
" <field name=\"propertySecurityTab\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"propertyAttachmentsTab\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"parentCardAccess\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"propertyTaskLogTab\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"confirmRequired\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"propertyCardCommentTab\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"parentCard\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"refuseEnabled\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"reassignEnabled\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"fullDescr\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"priority\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"propertyCardProjectsTab\" visible=\"%s\" required=\"false\" signed=\"false\"/>\n" +
" <field name=\"propertyCardRelationsTab\" visible=\"true\" required=\"false\" signed=\"false\"/>\n" +
"</fields>\n";
@BeforeEach
void before() {
tasksFactory = environment.getContainer().getEntityFactory(Task.class, TestEntityState.NEW);
typesFactory = environment.getContainer().getEntityFactory(TaskType.class, TestEntityState.DETACHED);
taskPatternsFactory = environment.getContainer().getEntityFactory(TaskPattern.class, TestEntityState.DETACHED);
projectsFactory = environment.getContainer().getEntityFactory(Project.class, TestEntityState.DETACHED);
//init test entities
task = tasksFactory.create(Collections.emptyMap());
invisibleProjectsType = createType("invisible Projects", false);
invisibleProjectsType1 = createType("invisible Projects 1", false);
visibleProjectsType = createType("invisible Projects", true);
visibleProjectsType1 = createType("invisible Projects 1", true);
invisibleProjectsPattern = createTaskPattern("invisibleProjectsPattern", invisibleProjectsType, null);
visibleProjectsPattern0 = createTaskPattern("visibleProjectsPattern - 0", visibleProjectsType, null);
visibleProjectsPattern2 = createTaskPattern("visibleProjectsPattern - 2", visibleProjectsType, createProjects());
//mock services
new Expectations() {
{
imageRecognitionService.getRecognitionEnabled(); result = false;
}
{
taskmanService.getDefaultProc(); result = createTaskManProcess();
}
{
wfService.getAssignmentInfos((Card) any); result = Collections.emptySet();
}
{
legalDocIntegrationService.cardHasFormalizedDocuments((Card) any); result = false;
}
{
//noinspection ConstantConditions
unsecuredDataService.loadList((LoadContext<? extends Entity>) any); result = Collections.emptyList();
}
{
signatureService.getUseSignature(); result = false;
}
{
docService.isUserCanSelectMainAttachments((Card)any); result = false;
}
{
thesisReportService.isReportAvailableForCurrentUser(anyString); result = false;
}
{
dataService.load(withInstanceOf(LoadContext.class));
result = new Delegate<DataService>() {
<E extends Entity> E load(LoadContext<E> context) {
if ("tm$TaskPattern".equals(context.getMetaClass())) {
if (invisibleProjectsPattern.getId().equals(context.getId())) {
return (E)invisibleProjectsPattern;
}
if (visibleProjectsPattern0.getId().equals(context.getId())) {
return (E)visibleProjectsPattern0;
}
if (visibleProjectsPattern2.getId().equals(context.getId())) {
return (E)visibleProjectsPattern2;
}
}
return null;
}
};
}
};
TestServiceProxy.mock(ImageRecognitionService.class, imageRecognitionService);
TestServiceProxy.mock(TaskmanService.class, taskmanService);
TestServiceProxy.mock(WfService.class, wfService);
TestServiceProxy.mock(LegalDocIntegrationService.class, legalDocIntegrationService);
TestServiceProxy.mock(UnsecuredDataService.class, unsecuredDataService);
TestServiceProxy.mock(SignatureService.class, signatureService);
TestServiceProxy.mock(DocService.class, docService);
TestServiceProxy.mock(ThesisReportService.class, thesisReportService);
TestServiceProxy.mock(DataService.class, dataService);
}
Proc createTaskManProcess() {
TsProc proc = environment.getContainer().getBean(Metadata.class).create(TsProc.class);
proc.setName("Управление задачами");
proc.setCode(TaskManagementConstants.PROC_NAME);
proc.setJbpmProcessKey(TaskManagementConstants.PROC_NAME);
proc.setLocName(MultiLanguageString.create("{\"captionWithLanguageList\":[{\"language\":\"ru\",\"caption\":\"Управление задачами\"},{\"language\":\"en\",\"caption\":\"Task management\"}]}"));
return proc;
}
@Test
public void hideCardProjectsTabTest() {
WindowManager windowManager = environment.getUI().getWindowManager();
WindowConfig windowConfig = AppBeans.get(WindowConfig.NAME);
WindowInfo windowInfo = windowConfig.getWindowInfo("tm$Task.edit");
TestTaskEditor editor = (TestTaskEditor) windowManager.openEditor(windowInfo, task, WindowManager.OpenType.NEW_TAB, Collections.emptyMap());
LookupField taskTypeLookup = (LookupField) editor.getWindow().getComponentNN("taskType");
TabSheet tabSheet = (TabSheet) editor.getWindow().getComponentNN("tabsheet");
CollectionDatasource<Project, UUID> cardProjectsDs = (CollectionDatasource<Project, UUID>) editor.getDsContext().getNN("cardProjectsDs");
Set<Project> projects = createProjects();
//Test 1. by default task type is empty, projects are empty, and cardProjectsTab is hidden
Assertions.assertNull(taskTypeLookup.getValue());
Assertions.assertEquals(0, cardProjectsDs.size());
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
//Test 2. No meter of projects quantity, tab is invisible according to TaskType
cardProjectsDs.addItem(projects.iterator().next());
taskTypeLookup.setValue(invisibleProjectsType);
editor.doChangeTaskType();
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
Assertions.assertFalse(tabSheet.getTab("cardProjectsTab").isVisible());
Assertions.assertNull(editor.getCardTabSheetHelper().getHiddenTabs().get("cardProjectsTab"));
//Test 3. Change task type from invisible projects tab to visible (cardProjectsDs has 1 item)
taskTypeLookup.setValue(visibleProjectsType);
editor.doChangeTaskType();
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
Assertions.assertTrue(tabSheet.getTab("cardProjectsTab").isVisible());
Assertions.assertNull(editor.getCardTabSheetHelper().getHiddenTabs().get("cardProjectsTab"));
//Test 4. Fill by task pattern with invisible cardProjectsTab
editor.fillTask(invisibleProjectsPattern);
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
Assertions.assertFalse(tabSheet.getTab("cardProjectsTab").isVisible());
Assertions.assertNull(editor.getCardTabSheetHelper().getHiddenTabs().get("cardProjectsTab"));
//Test 5. Fill by task pattern with visible cardProjectsTab and 2 projects
editor.fillTask(visibleProjectsPattern2);
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
Assertions.assertTrue(tabSheet.getTab("cardProjectsTab").isVisible());
Assertions.assertNull(editor.getCardTabSheetHelper().getHiddenTabs().get("cardProjectsTab"));
//Test 6. Fill by task pattern with visible cardProjectsTab and 0 projects
editor.fillTask(visibleProjectsPattern0);
Assertions.assertTrue(editor.getCardTabSheetHelper().hasTab(tabSheet, "cardProjectsTab"));
Assertions.assertNull(tabSheet.getTab("cardProjectsTab"));
Assertions.assertNotNull(editor.getCardTabSheetHelper().getHiddenTabs().get("cardProjectsTab"));
}
TaskType createType(String name, boolean visibleCardProjectsTab) {
return typesFactory.create(
"name", name,
"code", name,
"entityType", "tm$Task",
"additionalFieldsTabName", "Доп. поля",
"fieldsXml", String.format(FIELDS_XML_PATTERN, visibleCardProjectsTab),
"createdBy", "admin"
);
}
TaskPattern createTaskPattern(String patternName, TaskType taskType, Set<Project> projects) {
return taskPatternsFactory.create(
"patternName", patternName,
"taskName", "FreakingTask",
"taskType", taskType,
"projects", CollectionUtils.isEmpty(projects) ? Collections.emptySet() : projects,
"project", CollectionUtils.isNotEmpty(projects) ? projects.iterator().next() : null,
"createdBy", "admin"
);
}
Set<Project> createProjects() {
ProjectGroup projectGroup = environment.getContainer().getBean(Metadata.class).create(ProjectGroup.class);
projectGroup.setName("main group");
Set<Project> projects = new HashSet<>(2);
projects.add(projectsFactory.create("name", "1", "projectGroup", projectGroup));
projects.add(projectsFactory.create("name", "2", "projectGroup", projectGroup));
return projects;
}
}
Запуск тестов
Тесты можно запускать несколькими способами.
Запуск через Gradle в IDE
Тесты можно запустить через окно Gradle в IDE.
Если выполнить задачу test для всего проекта в ветке Tasks, поиск и запуск тестов будет выполнен во всех модулях проекта.
Также можно запустить задачу test для отдельного модуля:
-
app-core — для тестов слоя core;
-
app-web — для тестов слоя web.
Ход выполнения тестов отображается на вкладке Run.Запуск через терминал
Тесты можно запустить из терминала с помощью команд:
gradlew test gradlew :app-core:test gradlew :app-web:testНазначение команд:
-
gradlew test— запуск всех тестов во всех модулях; -
gradlew :app-core:test— запуск тестов модуля core; -
gradlew :app-web:test— запуск тестов модуля web.b модуля
-
Результаты выполнения отображаются в терминале.
Запуск из редактора IDE
Один тест или все тестовые методы класса можно запустить средствами IDE. Для этого откройте тестовый класс в редакторе.
Рядом с объявлением класса и рядом с каждым методом, отмеченным аннотацией @Test, отображается значок запуска.
При нажатии на него можно выбрать вариант запуска.
Этот способ удобен, если необходимо выполнить отдельный тест или запустить проблемный тест в режиме отладки.
Ход выполнения отображается на вкладке Run.




