Русский
Русский
English
Статистика
Реклама

Intellij idea

Создаем plugin для IDEA для мониторинга транзакций в Spring

20.04.2021 10:21:38 | Автор: admin

Disclaimer: я не являюсь сотрудником JetBrains (а жаль), поэтому код может являться не оптимальным и служит только для примера и исследовательских целей.

Введение

Часто во время работы со Spring непонятно, правильно ли работает аннотация @Transaction:

  • в правильном ли месте мы ее поставили

  • правильно ли объявился interceptor

  • и т.д.

Самым простым способом для меня было остановиться в debug в IDEA в необходимом методе и исследовать, что возвращает

TransactionSynchronizationManager.isActualTransactionActive();

Но "я же программист" и захотелось это дело хоть как-то автоматизировать, заодно поизучать возможности написания plugin для IDEA.

Хочется иметь семафор, который визуально показывает, активна ли транзакция или нет, а также иметь возможность сделать срез основных свойств для анализа в будущем.

Примерно вот что получилось:

Подробней про транзакции можно прочитать здесь.

С чего начать создания plugin?

Про настройку проекта для создание plugin можно прочитать здесь.

Повторюсь, что главными отправными точками будут:

Реализация

Исходный код получившегося решения размещен здесь.

Создание action

Прочитать про action можно здесь. Под action, в основном, понимается кнопка или элемент меню.

Сначала определим новый action. У action есть два метода update и actionPerformed.

  • update - вызывается idea несколько раз в секунду для того, чтобы установить корректное состояние (включен/не включен, виден/не виден)

  • actionPerformed - вызывается idea при выполнении действия (нажатия на кнопку).

public class PopupDialogAction extends AnAction {  @Override  public void update(AnActionEvent e) {    // Using the event, evaluate the context, and enable or disable the action.  }  @Override  public void actionPerformed(@NotNull AnActionEvent e) {    // Using the event, implement an action. For example, create and show a dialog.  }}

Для того, чтобы зарегистрировать новый action, требуется прописать в plugin.xml:

<actions>  <action id="TransactionStatusAction"          class="com.github.pyltsin.sniffer.debugger.TransactionStatusAction"          icon="SnifferIcons.RUNNING"          text="Current Transaction Status">    <add-to-group group-id="XDebugger.ToolWindow.TopToolbar" anchor="last"/>  </action></actions>

После этого должна была появиться новая кнопка на панели инструментов в debug-окне

Вычисление значений в debug

IDEA позволяет нам вычислять выражения при debug и взаимодействовать с памятью

И не только

Информация взята из twitter Тагира Валеева

В Evaluate даже встроен свой мини-интерпретатор Java, который позволяет выполнять прикольные вещи, например, такие:

Картинка из поста Тагира Валеева (http://personeltest.ru/aways/twitter.com/tagir_valeev/status/1360512527218728962)Картинка из поста Тагира Валеева (http://personeltest.ru/aways/twitter.com/tagir_valeev/status/1360512527218728962)

Поэтому, используя API IDEA, мы легко сможем узнать, когда транзакция активна, выполнив:

const val TRANSACTION_ACTIVE: String =    "org.springframework.transaction.support.TransactionSynchronizationManager"+  ".actualTransactionActive.get()==Boolean.TRUE"

Для начала сделаем так, чтобы наше действие было недоступно, если мы не находимся в режиме debug. Для этого получим XDebugSession и сравним с null

override fun update(e: AnActionEvent) {  val presentation = e.presentation  val currentSession: XDebugSession? = getCurrentSession(e)  if (currentSession == null) {    setDisabled(presentation)    return  }}private fun getCurrentSession(e: AnActionEvent): XDebugSession? {  val project = e.project  return if (project == null) null else   XDebuggerManager.getInstance(project).currentSession}

Многие вещи в idea реализованы через статические методы и паттерн singleton (хотя это уже почти считается антипаттерном - это очень удобно, что мы можем получить требуемые значения из любого места через статические методы, например, XDebuggerManager.getInstance)

Евгений Борисов не одобряет singleton как pattern, когда есть SpringЕвгений Борисов не одобряет singleton как pattern, когда есть Spring

Теперь мы хотим получить значения из контекста Spring из текущей сессии Java. Для этого можно воспользоваться следующим методом:

public abstract class XDebuggerEvaluator {  public abstract void evaluate(@NotNull String expression,                                 @NotNull XEvaluationCallback callback,                                 @Nullable XSourcePosition expressionPosition);}

Например, так

val currentSourcePosition: XSourcePosition? = currentSession.currentStackFrame?.sourcePositioncurrentSession.debugProcess.evaluator?.evaluate(  TRANSACTION_ACTIVE, object : XDebuggerEvaluator.XEvaluationCallback {    override fun errorOccurred(errorMessage: String) {      TODO("Not yet implemented")    }    override fun evaluated(result: XValue) {      TODO("Not yet implemented")    }  },  currentSourcePosition)

В XValue теперь хранится вычисленное значение. Чтобы посмотреть его, можно выполнить:

(result as JavaValue).descriptor.value

Он возвращает объект класса - com.sun.jdi.Value

Часть JavaDoc для com.sun.jdi.Value

The mirror for a value in the target VM. This interface is the root of a value hierarchy encompassing primitive values and object values.

Мы научились вычислять значения в debug с использованием API IDEA.

Рабочая панель (Tool Window)

Теперь попробуем их вывести в рабочую панель (Tool Window). Как всегда начинаем с документации и примера.

Объявляем в plugin.xml новое окно

<toolWindow id="TransactionView" secondary="true" icon="AllIcons.General.Modified" anchor="right"factoryClass="com.github.pyltsin.sniffer.ui.MyToolWindowFactory"/>
public class MyToolWindowFactory implements ToolWindowFactory {  public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {    MyToolWindow myToolWindow = new MyToolWindow(toolWindow, project);    ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();    Content content = contentFactory.createContent(myToolWindow.getContent(), "", false);    final ContentManager contentManager = toolWindow.getContentManager();    contentManager.addContent(content);  }}

Само окно можно нарисовать во встроенном редакторе

IDEA сама сгенерирует для нас заготовку класса:

А дальше, вспоминая старый добрый Swing, описываем логику и добавляем необходимые Listener.

Получившееся окно инструментов (Tool Windows)Получившееся окно инструментов (Tool Windows)

Способы передачи данных

Вернемся к нашему action. При нажатии на кнопку вызывается метод actionPerformed.

Как из этого метода достучаться до нашего окна?

Самый простой способ - снова воспользоваться статическим методом:

val toolWindow: ToolWindow? =ToolWindowManager.getInstance(project).getToolWindow("TransactionView")

И передать туда требуемые значения.

IDEA предоставляет еще один способ - Message Bus (детальное описание лучше смотреть в документации). Один из вариантов использования следующий:

Объявить интерфейс:

public interface ChangeActionNotifier {    Topic<ChangeActionNotifier> CHANGE_ACTION_TOPIC = Topic.create("custom name", ChangeActionNotifier.class)    void beforeAction(Context context);    void afterAction(Context context);}

В месте, где принимаем сообщения:

bus.connect().subscribe(ActionTopics.CHANGE_ACTION_TOPIC, new ChangeActionNotifier() {        @Override        public void beforeAction(Context context) {            // Process 'before action' event.        }        @Override        public void afterAction(Context context) {            // Process 'after action' event.        }});

В месте, где отправляем сообщения:

ChangeActionNotifier publisher = myBus.syncPublisher(  ActionTopics.CHANGE_ACTION_TOPIC);publisher.beforeAction(context);

В любом случае необходимо быть аккуратным с многопоточностью и не выполнять долгие операции на UI Thread (подробности).

Осталось собрать все вместе и протестировать.

Исходный код получившегося решения размещен здесь.

Краткий "как бы" вывод

IDEA очень гибкий и удобный инструмент для разработки, который можно быстро расширить требуемыми функциями. IDEA содержит массу интересного внутри себя, тем более ее код доступен всем.

Ссылки

Подробнее..

Вжух, и прогоны автотестов оптимизированы. Intellij IDEA плагины на службе QA Automation

16.12.2020 12:18:50 | Автор: admin


Привет, Хабр. Я работаю QA Automation инженером в компании Wrike и хотел бы поговорить о том, как нам удалось оптимизировать процесс код-ревью для репозитория с 30 000+ автотестов при помощи IntelliJ IDEA плагина. Я расскажу о внутреннем устройстве плагина и о том, какие проблемы он решает в нашей компании. А еще в конце статьи будет ссылка на Github репозиторий с кодом плагина, с помощью которого вы сможете попробовать встроить плагин в ваши процессы.

Автотесты и деплой в Wrike


Мы пишем много разнообразного кода на Java в проекте автотестов. Сейчас у нас существует более тридцати тысяч тестов, которые тестируют наш продукт через Selenium, REST API, WebSocket и т.д. Все тесты разбиты на множество Maven-модулей в соответствии со структурой кода фронтенда. Проект активно меняется (релизы 1-3 раза в день), и, чтобы поддерживать качество кода, мы используем хорошо развитый механизм код-ревью. Во время ревью проверяем не только качество, но и работоспособность тестов.

Автотесты это, конечно, важно, но без тестируемого продукта они бесполезны. В общем случае добавление новой функциональности в продукт подразумевает не только наличие веток фронтенда и бэкенда, но и ветки с тестами в проекте автотестов. Все ветки деплоятся одновременно. Ежедневно мы срезаем в мастер десятки веток в нескольких деплоях. Перед началом деплоя происходит запуск тестов. Если в результатах запуска обнаруживаются проблемы из-за ошибок в коде проекта автотестов, то это приводит к нежелательной задержке деплоя.

Чтобы избежать таких ситуаций, мы решили добавить в процесс код-ревью одно действие. Автор merge request должен приложить ссылку на зеленый прогон автотестов в Teamcity, а ревьюер перейти по ней и проверить, что прогон действительно зеленый.

Какие проблемы могут возникнуть при запуске автотестов


Но автора merge request могут поджидать неприятности даже в такой простой процедуре как добавление ссылки на прогон. Если автоматизатор меняет в проекте большое количество кода, то ему нелегко понять, какие именно тесты нужно прогонять, чтобы убедиться, что ничего не сломалось.

У нас есть два механизма запуска тестов:

  1. По группам продуктовая разметка вида Epic/Feature/Story.
  2. По идентификаторам (id) любой автотест помечается уникальным числом, и можно запускать прогон по набору таких чисел.

В большинстве случаев прогон бежит по продуктовой группе тестов: указать группу быстрее, чем собирать id автотестов по всему проекту. Автоматизатор пытается по изменениям в коде предположить, автотесты каких частей продукта он затронул.

Дальше события могут развиваться по нескольким сценариям.

Сценарий 1: Запущено меньше тестов, чем в действительности затронуто новым кодом.

Первая проблема потенциальное замедление процесса деплоя. Ведь есть риск, что пропущен тест, который перестал работать из-за изменений в коде. Тогда он обнаружится в день деплоя и затормозит доставку продукта пользователям.

Сценарий 2: Запущено больше тестов, чем в действительности затронуто новым кодом.

Вторая проблема избыточная работа инфраструктуры для запуска тестов, за которую платит компания. Это увеличивает очередь на запуск тестов в Teamcity, что заставляет дольше ждать результат прогона.

Чтобы решить эти проблемы, мы создали Find Affected Tests IntelliJ IDEA плагин, который быстро и надежно находит список id тестов, затронутых изменениями в коде проекта автотестов.

От бизнес-проблем к реализации


Но почему именно IntelliJ IDEA плагин?, спросите вы. Чтобы создать инструмент, который решит наши проблемы, мне пришлось ответить на два вопроса:

  • Откуда брать изменения кода?
  • Как по изменениям в коде найти затронутые id?

На первый вопрос ответ был очевиден. Мы используем Git в качестве VCS и через него можем получать информацию про состояние текущей ветки.

Ответом на второй вопрос стал PSI (Program Structure Interface). Я выбрал именно этот инструмент по нескольким причинам:

  1. Мы используем IntelliJ IDEA в качестве IDE.
  2. IntelliJ Platform SDK включает в себя PSI удобный инструмент для работы с кодом проекта, который используется и самой IntelliJ IDEA.
  3. Функциональность IntelliJ IDEA можно расширить за счет механизма плагинов.

Последняя причина и определила форму инструмента для поиска id автотестов плагин, который встроен в IDE и позволяет получать список id тестов, которые были затронуты в ходе разработки.

Структура плагина


Давайте посмотрим на UI плагина, чтобы представить, что видит автоматизатор перед запуском поиска id:


Так выглядит упрощенная версия, которая выложена на GitHub

Все интуитивно понятно: запустили поиск, посмотрели результат. Результат состоит из двух частей: id автотестов и списка Maven-модулей, в которых эти автотесты находятся (зачем нужен этот список я расскажу дальше в этой статье).

Внутренняя структура плагина состоит из двух основных модулей:

  • Git модуль получает информацию о текущей ветке и преобразует ее к формату, понятному PSI.
  • PSI модуль на основании данных от Git модуля ищет id тестов, которые затронуты изменениями в текущей ветке.

Взаимодействие UI-части плагина с модулями схематично выглядит так:


AffectedCodeUnit и DisplayedData это классы для передачи данных между модулями

Git модуль


Нам были важны изменения в коде относительно мастера, поэтому я решил собрать все коммиты из текущей ветки, которых нет в мастере. Каждый коммит включает в себя изменения некоторых файлов.

Для каждого файла собираются следующие данные:

  • Имя файла, имя модуля и путь до файла относительно корня проекта. Это позволяет в дальнейшем упростить поиск файла через механизмы PSI.
  • Номера измененных строк в файле. Это позволяет понять, что именно было изменено в структуре Java-кода.
  • Был ли файл удален или создан с нуля. Это помогает оптимизировать работу с ним в будущем и избежать ошибок в некоторых ситуациях (например, не обращаться к удаленному файлу через PSI).

Все эти сведения содержатся в выводе команды git diff. Пример результата выглядит так:


В выводе команды можно сразу заметить путь до измененного файла. Данные о номерах строк измененного кода содержаться в строках вида @@ -10 +10,2 @@. В этой статье я не буду подробно объяснять их смысл, но примеры можете посмотреть на Stackoverflow или поискать информацию про git diff unified format

Аргументами для git diff выступают текущая локальная ветка пользователя и remote master, а ряд ключей позволяет сократить вывод команды до нужного размера.

Каким образом происходит взаимодействие с Git в коде плагина? Я попытался найти встроенные механизмы IntelliJ IDEA, чтобы не изобретать велосипед. В итоге обнаружил, что для Git в IntelliJ IDEA существует свой плагин git4Idea. По сути это GUI для стандартных операций с проектом. Для доступа к Git в нашем плагине используются интерфейсы из кода git4Idea.

В итоге получилась такая схема:



Через класс GitBranch запрашивается diff, затем diff отправляется в DiffParser. На выходе остается список объектов класса AffectedCodeUnit (в них содержится информация об измененных файлах с кодом). О судьбе этого списка я расскажу в описании PSI модуля.

PSI модуль


Теперь информацию об измененных файлах нужно применить для поиска id автотестов. Чтобы разобраться, как по номерам измененных строк найти элементы Java-кода, нужно подробнее посмотреть на устройство PSI.

Как IntelliJ IDEA работает с произвольным проектом. Для PSI любой файл преобразуется в дерево отдельных элементов. Каждый элемент реализует интерфейс PsiElement и представляет собой структурную единицу кода для конкретного языка программирования. Это может быть целый класс или метод, а может быть закрывающая скобка небольшого блока кода.

Для нашей задачи элемент Java кода и есть PsiElement-объект. Здесь мы приходим к новой формулировке вопроса. Как по номерам измененных строк кода найти PsiElement-объекты? В интерфейсе PsiFile (представление файла с кодом в виде объекта) был унаследован метод findElementAt, который по сдвигу относительно начала текстового представления файла умел находить PsiElement.

Но возникла новая проблема: каждая строка в файле потенциально содержит немало символов, и по номеру строки можно получить много разных PsiElement-объектов.

Как выбрать нужный PsiElement-объект. Дерево, в которое IntelliJ IDEA отображает Java-код, может состоять из огромного числа узлов. Конкретный узел может описывать незначительный элемент кода. Для меня важны узлы конкретных типов: PsiComment, PsiMethod, PsiField и PsiAnnotation.

И вот почему:

String parsedText = parser.parse("Text to parse");

В этом фрагменте можно изменить разные части кода: имя переменной parsedText, входной параметр метода parse, код после оператора присваивания (вместо вызова parse можно вызвать другой метод) и т.д. Видов изменений много, но для логики работы плагина важно то, что фрагмент кода может находиться внутри какого-то метода, как в этом примере:

public String parseWithPrefix(Parser parser, String prefix) {    String parsedText = parser.parse("Text to parse");    return prefix + parsedText;}

В такой ситуации нет смысла пытаться понять, что поменялось в строчке кода с объявлением String parsedText. Нам важно, что изменился метод parseWithPrefix. Таким образом, нам не важна излишняя точность при поиске PsiElement-объектов для измененной строки кода. Поэтому я решил брать символ посередине строки как измененный и искать привязанный к нему PsiElement. Такая процедура позволила получить список затронутых PsiElement объектов и по ним искать id автотестов.

Конечно, можно придумать примеры Java-кода с диким форматированием, в котором нужно учитывать больше условий для поиска PsiElement-объекта. Как в этом примере:

public String parseWithPrefix(Parser parser, String prefix) {    String parsedText = parser.parse("Text to parse");    return prefix + parsedText; } private Parser customParser = new Parser();

Изменения в последней строке могут затрагивать как метод parseWithPrefix, так и поле customParser. Но у нас есть механизмы анализа кода, которые не допустят подобное в мастер.

Как устроен базовый алгоритм получения id автотестов по набору PsiElement-объектов. Для начала нужно уметь по PsiElement-объекту получать его использования (usages) в коде. Это можно сделать с помощью интерфейса PsiReference, который реализует связь между объявлением элемента кода и его использованием.

Теперь сформулируем краткое описание алгоритма:

  1. Получаем список PsiElement-объектов от Git модуля.
  2. Для каждого PsiElement-объекта ищем все его PsiReference объекты.
  3. Для каждого PsiReference объекта проверяем наличие id и сохраняем, если нашли.
  4. Для каждого PsiReference объекта ищем его PsiElement-объект и рекурсивно запускаем на нем описанную процедуру.

Процедуру следует повторять, пока находятся PsiReference-объекты.

Для задачи поиска абстрактной информации в коде алгоритм выглядит так:



В нашей задаче нужно найти id автотестов. Вот так можно применить блок-схему для этого:



Оптимизация алгоритма. В процессе реализации алгоритма я столкнулся с одной проблемой. Алгоритм работает с PsiTree-объектом, который состоит из большого числа узлов. Каждый узел соответствует элементам Java-кода. Механизм поиска PsiReference-объектов по умолчанию может находить довольно незначительные элементы Java-кода, из-за чего алгоритм будет совершать много лишних действий в процессе движения по дереву. Это замедляет работу алгоритма на несколько минут в тех случаях, когда найдено около тысячи тестов или больше.

Пример для наглядности: предположим, что значение переменной wrikeName изменилось:

public List<User> nonWrikeUsers(List<User> users) {    String wrikeName = "Wrike, Inc.";    return users.stream()                     .filter(user -> !wrikeName.equals(user.getCompanyName()))                     .collect(Collectors.toList());}

Тогда в поисках использования wrikeName мы сначала придем к вызову метода equals, затем к отрицанию результата вызова equals, а после к лямбде внутри вызова filter. Но эту длинную цепочку шагов можно заменить на поиск использований изменившегося метода nonWrikeUsers.

И тогда вместо четырех итераций алгоритма по каждому узлу дерева мы получим всего одну:



Проблему лишних операций в алгоритме удалось решить благодаря тому же ограничению типа данных PsiElement-объектов до PsiComment, PsiMethod, PsiField и PsiAnnotation. Именно эти PSI-сущности содержат всю релевантную информацию для поиска id затронутых автотестов.

Пример релевантной информации: затронуто поле какого-то класса (объект типа PsiField), оно могло использоваться в каком-то автотесте.

Пример нерелевантной информации: затронуто объявление локальной переменной (объект типа PsiLocalVariable). Здесь нам важно лишь то, где находится эта переменная (например, в теле другого метода). Само ее изменение нам ничего не дает.

В начало логики алгоритма я добавил поиск прародительского узла с нужным типом данных. Поиск осуществляется при помощи метода getParentOfType класса PsiTreeUtil. Для каждого типа PsiElement-объекта я создал реализацию логики обработки. Например, для прародителя типа PsiField отрабатывает объект класса FieldProcessing, а для PsiComment объект класса CommentProcessing. Код плагина исполняет подходящую логику в зависимости от результата работы метода getParentOfType.

В общем виде логика работы выглядит так:



Пример реализации логики обработки одного из типов PsiElement-объектов:



Подытожу основные основные особенности PSI модуля:

  1. Модуль работает по рекурсивному алгоритму поиска id автотестов. Краткая идея алгоритма заключается в поиске id для всех измененных PsiElement-объектов через механизм PsiReference.
  2. Алгоритм работает не со всеми узлами PsiTree-объекта, чтобы не совершать избыточные итерации.

Дополнительные возможности плагина


После того, как мы начали использовать плагин в работе, коллеги прислали несколько запросов по его улучшению. Это помогло усовершенствовать инструмент и оптимизировать прогоны автотестов.

Делаем работу с Git модулем гибче


В изначальной версии модуля можно было сравнивать текущую ветку с мастером (причем для ветки брался последний коммит). Но иногда это приводило к бесполезной работе инфраструктуры. А ведь именно с этим плагин и боролся.

Пример 1: автоматизатор создает merge request с одним коммитом. Он прогоняет 1000 тестов, которые затронуты этим коммитом. Ревьюер оставляет замечания по коду, автор merge request их исправляет. Эти правки затрагивают теперь только 200 тестов, но плагин предложит прогнать и 1000 тестов из изначального прогона, так как учтен первый коммит. Получаем лишние тесты для прогона.

Решение: я добавил возможность сравнивать локальную версию ветки с удаленной версией. Так будут учитываться только коммиты, которые еще не были запушены.

Пример 2: автоматизатор создает интеграционную ветку для того, чтобы мерджить туда другие ветки на протяжении некоторого времени (код в мастер в итоге будет добавлен из интеграционной ветки). На код-ревью ветка для мерджа в интеграционную ветку будет включать в себя коммиты оттуда. Но эти коммиты уже могли быть проверены раньше при помощи плагина. В итоге мы каждый раз получаем лишние тесты для прогона.

Решение: я реализовал функциональность для сравнения локальной версии ветки с произвольной веткой (в общем виде с любым коммитом). Это позволяет гибко учитывать коммиты при работе с несколькими ветками.

Помогаем внедрить новую оптимизацию с помощью плагина


Для понимания пользы второго улучшения нужно рассказать небольшую предысторию. Как я упоминал выше, наш проект состоит из огромного количества Maven-модулей. В какой-то момент рост числа Maven-модулей в нашем проекте привел к ожиданию при каждом запуске автотестов, так как код рекурсивно компилировался во всех модулях, даже если тесты нужны были только из одного. В этой ситуации снова возникла проблема бесполезной работы инфраструктуры.

Тогда мы решили изменить сценарий запуска. Теперь пересобираются только модули, указанные вручную. Это позволило в среднем снизить время компиляции с 20 минут до 3-4.

Чтобы узнать, в каких именно модулях находятся затронутые тесты, я добавил в плагин логику вывода затронутых модулей. Для ее реализации во время получения id автотеста нужно взять пути до файла с тестом в операционной системе и относительно корня проекта. Зная структуру директорий типичного Maven проекта, можно обрезать все лишнее и оставить часть пути с именами модулей.

В итоге удалось быстрее внедрить новую логику запуска и как результат уменьшить время ожидания прогона тестов и КПД инфраструктуры.

Планы по развитию плагина


Плагин выходит в сеть. Мы автоматизировали сбор id затронутых тестов, но после автоматизатору все еще приходится выполнять набор однотипных действий для запуска прогона и дальнейшей работы с ним:

  1. Открыть нужный билд в Teamcity.
  2. Ввести данные для прогона.
  3. Сохранить ссылку на прогон для проверки его результатов.

Это отвлекает от решения других задач и приводит к возможным ошибкам: запуск в неверной сборке Teamcity, ошибки при копировании данных для прогона.

Так возникла задача: научить плагин запускать прогон в Teamcity самостоятельно. У нас есть специальный инструмент, который аккумулирует большинство данных, связанных с CI/CD процессами. Поэтому плагину проще отсылать запрос на запуск прогона этому инструменту (в нем же будет сохранена ссылка на прогон). Реализация этой логики позволит уменьшить количество рутинных действий после написания кода и сделает процесс запуска автотестов надежнее.

Многопоточность. При большом количестве изменений время работы плагина сильно возрастает, поэтому у нас есть планы по дальнейшей оптимизации, связанные с многопоточностью.

Идея состоит в том, чтобы несколько потоков одновременно занимались анализом разных узлов Psi-дерева. Для этого нужно грамотно выстроить работу с общими ресурсами (например, со множеством уже обработанных PsiElement-объектов). Одним из возможных решений может быть Java Fork/Join Framework, так как работа с деревом подразумевает рекурсивность. Но здесь есть подводные камни: у IntelliJ IDEA есть свой пул потоков, в который нужно грамотно встроить новую логику.

Несколько выводов


Как плагин повлиял на проблемы, для решения которых он создавался? Время деплоя очень сложная величина, на которую влияет огромное количество факторов. Пожалуй, не существует метрик, отражающих прямое влияние плагина на нее. Поэтому перейдем сразу к проблеме запуска автотестов в процессе код-ревью.

У нас есть сборка в Teamcity, в которой традиционно гоняются запущенные вручную автотесты. Она позволяет запустить прогон с указанием Maven-модулей. Я выбрал эту сборку для примера, потому что ссылку именно на нее автор merge request прикладывает во время код-ревью. А еще запуски в этой сборке выполняются вручную, и автоматизатор может взять набор id для запуска только из плагина (вряд ли кто-то в здравом уме будет собирать их руками).

Так выглядит график общего количества запусков в этой сборке:



В марте этого года (точка 03.2020) мы анонсировали сборку официально, а в апреле (точка 04.2020) я добавил в плагин вывод Maven-модулей, в которых находились затронутые автотесты. После этого количество запусков возросло в несколько раз. Следующий скачок произошел в сентябре, так как эта сборка стала использоваться для перезапуска автотестов в нашей внутренней деплой туле, но это нам не так интересно.

А этот график показывает, что запуски происходят именно по id, а не по продуктовой разметке. На нем указано процентное отношение между прогонами, в которых указаны id автотестов, и всеми прогонами для данной сборки:



За последние полгода порядка 70-80% запусков автотестов происходят с указанием списка id: коллеги активно используют плагин по назначению. В апреле мы добавили в плагин вывод Maven-модулей. На графике видно, что это увеличило процент запусков с id с 50% до 75% за пару месяцев.

Действительно ли прогоны по id быстрее прогонов по продуктовой разметке? Статистика среднего времени прогона для данной сборки показывает, что да. Для id мы получаем примерно в три раза меньшее время, чем для продуктовой разметки: вместо условных 25-30 минут 8-10 минут.

Данные на графиках доказывают, что плагин активно используют. А это значит, что нам удалось увеличить количество прогонов в Teamcity, в которых запущенные автотесты подобраны по более надежному алгоритму, чем просто запуск по продуктовой разметке.

Пишите в комментариях про ваши успешные или не очень успешные примеры автоматизации.

Если вас заинтересовала идея плагина (возможно, у вас даже появились мысли о том, где его можно применить), то можете воспользоваться упрощенным кодом его основной версии (тут же выложен проект-заглушка для тестирования плагина). С удовольствием выслушаю замечания и предложения по архитектуре кода.

Всем добра и побольше эффективных оптимизаций!
Подробнее..

JPA Buddy Умный помощник половина работы

17.03.2021 14:12:14 | Автор: admin

От переводчика: это статья моего коллеги @aleksey-stukalov, которую мы опубликовали в блоге JPA Buddy пару месяцев назад. С тех пор мы выпустили JPA Buddy 2.0, но все сказанное в этой статье актуальности не потеряло.

Ну что ж, Hello World... После почти года разработки наконец-то вышла первая версия JPA Buddy! Это инструмент, который должен стать вашим верным помощником по написанию кода для проектов с JPA и всем, что с этим связано: Hibernate, Spring Data, Liquibase и другим ПО из типичного стека разработки.

Чем он вам поможет? Если кратко, JPA Buddy упростит работу с JPA и тем самым сэкономит ваше время. В этой статье мы взглянем на основные фичи JPA Buddy, немного обсудим его историю и поговорим о его преимуществах. Надеюсь, он займет достойное место среди любимых инструментов Java-разработчиков, которые пользуютсяJPA, Spring, Liquibase и, конечно же, самой продвинутой Java IDE IntelliJ IDEA.

Откуда растут ноги

Мы создатели CUBA Platform (кстати, не так давно мы переименовали ее в Jmix :)) среды быстрой разработки приложений на Java. Платформа CUBA уникальный продукт. Он состоит из двух частей: фреймворка и инструмента разработки CUBA Studio. Одной из самых полюбившихся частей CUBA Studio стал Entity Designer. В сообществе CUBA более 20 000 разработчиков, и почти все они отмечают, насколько легко и быстро он дает создавать модель данных. Даже для тех, кто никогда не слышал о JPA, такие вещи как создание JPA-сущностей, ограничений модели данных и DDL-скриптов становятся легкой задачей.

В 2016 году CUBA стала open-source проектом. С того момента мы приняли участие в десятках конференций по всему миру, собрали много отзывов и лучше поняли, что именно нужно разработчикам. Они нас часто спрашивали: Можно ли использовать ваш конструктор сущностей без CUBA Platform?. Рады сообщить, что с появлением JPA Buddy мы теперь можем смело ответить да!.

JPA Buddy не требует дополнительных зависимостей это просто инструмент. Вы можете пользоваться им не только на новых, но и на уже существующих проектах, которые используют JPA. Это плагин для IntelliJ IDEA, который помогает вам писать связанный с JPA код, находить и исправлять потенциальные ошибки, проводить рефакторинг и выполнять другие трудоемкие, но достаточно шаблонные операции.

Область применения

Перед тем, как написать первую строчку исходного кода JPA Buddy, мы провели опрос и собрали различные сценарии использования JPA и сопутствующих технологий. Результат оказался достаточно предсказуемым: среднестатистическое приложение в настоящее время это приложение на Spring Boot с Hibernate в качестве реализации ORM, Spring Data JPA в качестве механизма управления данными и Flyway или Liquibase для системы миграции базы данных. Ах да, чуть не забыли про Lombok... В общем, на этом стеке мы и сконцентрировались в первом релизе.

Говоря о предназначении JPA Buddy, мы поставили перед собой следующие цели:

  • Сократить написание шаблонного кода вручную: инструмент должен генерировать код быстрее ручного ввода

  • Не заставлять тратить время на чтение документации: инструмент должен предоставлять интуитивно понятные визуальные конструкторы

  • Оставить свободу выбора: инструмент должен не диктовать какойлибо конкретный стиль написания кода, а поддерживать его различные варианты

  • Выявлять потенциальные проблемы и предлагать решения для наиболее распространенных: инструмент должен предупреждать разработчика о проблемах на этапе написания кода, а не в рантайме

  • Обеспечить обзор проекта с точки зрения данных и удобную навигацию между связанными сущностями

В первом релизе мы смогли реализовать достаточно значительный объем функциональности, охватывающей большинство аспектов разработки модели данных. Хорошая новость для тех, кто использует Liquibase: инструменты для работы с ним уже реализованы и доступны. Не очень хорошая новость для пользователей Flyway: он входит в список наиболее приоритетных фич :)

В долгосрочной перспективе мы собираемся реализовать следующие функции:

  • Аннотации Hibernate: @Where, @NaturalId, @Formula, поисковые аннотации и т. п.

  • Визуальный конструктор запросов

  • Аудит с использованием Envers и Spring Data JPA

  • Генерация модели по существующей схеме базы данных

  • Поддержка Kotlin

В дальнейшем мы учтем и другие функции: поддержка Quarkus и Micronaut, REST API и генерация пользовательского интерфейса для CRUD-операций.

Как видите, впереди еще долгий путь, и мы были бы очень признательны за вашу помощь в расстановке приоритетов разработки не стесняйтесь высказывать свою точку зрения через нашу форму, Twitter или Discord.

Общий обзор

Давайте посмотрим, как выглядит JPA Buddy. После его установки у вас появятся 3 новые панели инструментов: JPA Structure, JPA Palette и JPA Inspector.

JPA Structure

Панель JPA Structure расположена в левом нижнем углу. Она позволяет посмотреть на проект с точки зрения модели данных. С ее помощью можно:

  1. Перемещаться по модели данных. Структура сущностей представлена в иерархическом виде. По ней легко перейти к объектам, ссылающимся на текущий объект, и к тем, на которые ссылается текущий объект. Это особенно полезно для тех, кто только начинает погружаться в существующий проект с большим графом сущностей, или для код-ревьюеров, которые видят часть модели впервые и им нужно в ней быстро разобраться.

  2. Создавать объекты, связанные с данными: сущности, JPA-конвертеры/Hibernate-типы, Spring Data репозитории и Liquibase-скрипты.

  3. Увидеть, для каких сущностей созданы какие репозитории.

  4. Просматривать скрипты Liquibase и их внутреннюю структуру.

  5. Настраивать параметры плагина, такие как соединение с БД, Persistence Units и некоторые другие, которые плагин не обнаружил автоматически.

JPA Palette и JPA Inspector

Панель JPA Palette находится справа вверху, и ее содержимое зависит от контекста. Она доступна только тогда, когда Buddy готов предложить чтото для быстрой генерации кода. Сейчас панель появляется при редактировании следующих объектов: JPA Entity, Spring Data репозиториев и Liquibase-скриптов. Для генерации какого-либо элемента просто выберите нужный вариант в списке и кликните по нему дважды.

JPA Inspector размещается справа внизу, под JPA Palette, и открывается вместе с ним. С помощью JPAPalette можно генерировать новый код, а с помощью JPAInspector редактировать уже существующий. В нем видно, как можно сконфигурировать выбранную часть кода, будь то поле сущности или выражение изLiquibase-скрипта.

Все это будет полезно как для новичков, так и для опытных разработчиков. Первые могут посмотреть на полный список вариантов, выбрать подходящий и сгенерировать корректный код с помощью понятных визуальных конструкторов. Сеньоры, в свою очередь, часто забывают мелочи, которые не используются в повседневной жизни, и Buddy сэкономит несколько секунд драгоценного времени на поиск в документации правильной аннотации или синтаксиса.

Интеграция с Liquibase

Хорошие новости для тех, кто использует Liquibase для управления версиями схемы базы данных: JPA Buddy тесно с ним интегрирован, он позволяет удобно редактировать и создавать Liquibase-скрипты, а также гибко управлять типами атрибутов. Давайте посмотрим, как именно JPA Buddy поможет вам работать с Liquibase.

Во-первых, в JPA Buddy есть визуальный конструктор для редактирования Liquibase-скриптов. Он показывает различные доступные команды плагина в JPAPalette, а панель инструментов JPAInspector дает увидеть настройки, которые можно применить к выбранному выражению.

Во-вторых, JPA Buddy дает определить свои собственные маппинги для сопоставления Java-типов (Конвертеров/Hibernateтипов) и типов, которые должны использоваться для каждой конкретной СУБД. Приведем несколько примеров, когда эта функция будет полезна:

  • Допустим, у вас в сущности есть поле типа byte[]. Что генератор схемы Hibernate, что Liquibase сопоставят ваш массив с довольно экзотическим типом OID. Думаю, многие разработчики предпочли бы использовать bytea вместо предлагаемого по умолчанию типа. Это можно легко сделать, создав маппинг с Java-типа byte[] на БД-тип bytea в настройках проекта в JPA Buddy.

  • Использование JPA-конвертера или кастомного Hibernate-типа вызывает непредсказуемое поведение или даже ошибки в стандартных генераторе схемы Hibernate и генераторе скриптов Liquibase. С помощью JPA Buddy можно явно указать нужный тип, чтобы решить эту проблему.

  • Поля типа String по-умолчанию хранятся как varchar, то есть не поддерживают Юникод. С помощью JPA Buddy легко изменить этот тип на nvarchar, с которым этой проблемы нет.

Все эти случаи обычно решаются с помощью атрибута columnDefinition, однако это решение не будет работать для проектов, где одновременно используется несколько СУБД. JPA Buddy позволяет указать, какой именно маппинг использовать для конкретной СУБД.

Наконец, функция, экономящая наибольшее количество времени наш генератор Liquibase-скриптов. Исходя из нашего опыта (который совпадает с опытом разработчиков принявших участие в ранее упомянутом опросе), есть два основных способа создания Liquibase-скриптов:

  • Написание вручную

  • Создание скриптов путем сравнения двух баз данных: исходной (представляющей фактическое состояние модели) и целевой (с предыдущим состоянием модели)

Для тех, кто предпочитает первый вариант, JPA Buddy включает уже упомянутый ранее конструктор Liquibase-скриптов.

Однако, второй вариант наиболее распространен. Итак, сравниваем две базы данных. Целевую базу данных относительно легко получить, например, можно просто создать дамп базы данных продакшена. Сложности возникают с получением исходной базы данных.

Начнем с небольшой проблемы: может случиться так, что база данных, используемая для разработки на ноутбуке разработчика, не того же типа, что и база данных на продакшене. Это можно исправить, например, запустив MS SQL в Docker на Mac.

Более серьезная проблема состоит втом, что трудно найти чистую исходную базу данных, поскольку она обычно формируется впроцессе разработки генератором схемы Hibernate. Часто это приводит кбольшому количеству мусорных таблиц иатрибутов, которые вконечном итоге появляются вбазе данных продакшена. Чтобы избежать подобного рода мусора, приходится тратить еще час или два напросмотр схемы базы данных перед запуском генерации Liquibase-скриптов.

ВJPA Buddy есть замечательная функция генерации Liquibase-скриптов напрямую, путем сравнения ваших объектов JPA сцелевой базой данных (или snapshotом целевой базы данных). Выможете использовать H2в целях разработки ипо-прежнему генерировать правильные журналы изменений для Oracle, MSSQL или того, что выиспользуете впродакшене. Подобный подход гарантирует, что если увас есть мусор вжурналах изменений, это именно тот мусор, который есть увас висходном коде. Все, что вам нужно, это содержать модель данных вчистоте, итогда миграция неприведет кнежелательным артефактам вбазе данных продакшена.

Еще одна особенность генератора журнала изменений это возможность фильтровать полученные выражения по 3 категориям:

  • содержащие только безопасные операторы, которые не могут дать сбой при обновлении или полностью уронить ваше приложение, например, добавление нового столбца

  • содержащие инструкции, которые могут завершиться ошибкой при обновлении, но не привести к потере данных, например, применение ограничения уникальности

  • содержащие операторы, которые вызовут потерю данных, например, удаление таблицы или столбца

Вы сами решаете, как разделить такие утверждения: поместить их в отдельные скрипты Liquibase или просто выбрать им нужную метку или контекст в редакторе.

Заключение

В этой статье мы посмотрели на первый релиз JPA Buddy. Очевидно, что как и любой новый продукт, JPA Buddy не может полностью избежать проблем и багов, особенно в своем первом релизе. Сейчас мы концентрируемся на устранении замечаний от пользователей, улучшении существующей функциональности, определении наиболее приоритетных функций, которые мы могли упустить из виду, и их реализации.

Говоря прямо, мы хотели бы вдохновить вас стать первыми пользователями JPA Buddy и сформировать сообщество энтузиастов. Установите JPA Buddy, попользуйтесь им и поделитесь своими отзывами с нашей командой разработчиков: это очень поможетнам выбрать правильное направление развития продукта. Также подпишитесь на наш Twitter: там мы показываем, какие еще фичи есть у JPA Buddy и как ими пользоваться. Думаю, вы найдете что-то полезное именно для вас.

Подробнее..

Перевод Java 15 и IntelliJ IDEA

07.06.2021 16:20:09 | Автор: admin

В Java 15 появились sealed-классы и sealed-интерфейсы, с помощью которых стало возможным ограничивать иерархию классов и интерфейсов на уровне синтаксиса языка. Теперь возможные иерархии определяются декларативно. Этот функционал пока представлен в режиме превью (preview).

Также в Java 15 есть изменения в записях (Records), появившихся в Java 14. А сопоставление с образцом (pattern matching) для instanceof вошло в Java 15 как второе превью без изменений. Текстовые блоки (text block) из Java 13 включены в Java 15 как стандартная языковая конструкция. Изменений в них по сравнению с Java 14 нет.

В этой статье я расскажу обо всех новых и обновленных языковых конструкциях Java 15, о том, как они вам могут пригодиться, и как их использовать в IntelliJ IDEA. Давайте начнем.

Sealed-классы и интерфейсы

Определяя класс как sealed, вы можете явно указать, каким классам разрешено его расширять. Это, с одной стороны, позволяет использовать класс повторно через наследование, а с другой ограничить допустимых наследников. Но зачем вам ограничивать иерархии наследования?

Необходимость ограниченных иерархий

Представьте, что вы разрабатываете приложение для садовников. Садовнику, в зависимости от вида растения, требуется выполнять различные действия. Давайте смоделируем иерархию растений следующим образом (я намеренно не привожу полный текст классов):

class Plant {}class Herb extends Plant {}class Shrub extends Plant {}class Climber extends Plant{}class Cucumber extends Climber {}

Ниже приведен пример того, как класс Gardener (садовник) может использовать эту иерархию классов:

public class Gardener {   int process(Plant plant) {       if (plant instanceof Cucumber) {           return harvestCucumber();       } else if (plant instanceof Climber) {           return sowClimber();       } else if (plant instanceof Herb) {           return sellHerb();       } else if (plant instanceof Shrub) {           return pruneShrub();       } else {           System.out.println("Unreachable CODE. Unknown Plant type");           return 0;       }   }   private int pruneShrub() { .. }   private int sellHerb() { .. }   private int sowClimber() { .. }   private int harvestCucumber() { .. }}

Проблема здесь в том, что разработчику необходимо предусмотреть ветку else для контроля ситуации, когда другой разработчик добавит класс в эту иерархию. Sealed-классы помогают наложить необходимые ограничения на уровне языка.

Определение защищенных иерархий с помощью sealed-классов

Объявить sealed-класс можно с помощью модификатора sealed. Для указания классов, которые могут его расширять напрямую, используется ключевое слово permits. Подклассы могут быть final, non-sealed или sealed.

Gif'ка ниже показывает, как изменить объявление обычного класса на sealed-класс и модифицировать его наследников:

Вот измененный код:

sealed public class Plant permits Herb, Shrub, Climber {}final class Herb extends Plant {}non-sealed class Shrub extends Plant {}sealed class Climber extends Plant permits Cucumber{}final class Cucumber extends Climber {}

Позволяя расширять класс только определенному перечню классов, вы можете отделить доступность (accessibility) от расширяемости (extensibility). Можно сделать sealed-класс доступным для других пакетов и модулей и контролировать, кто может его расширять. В прошлом, чтобы предотвратить расширение классов, разработчики создавали package-private классы. Однако это также ограничивало к ним доступ. Для sealed-классов это уже не так.

Перечень permitted-подклассов доступен через рефлексию (reflection) с помощью метода Class.permittedSubclasses(). Можно получить всю sealed-иерархию в рантайме.

Давайте быстро проверим вашу конфигурацию IntelliJ IDEA, чтобы убедиться, что вы сможете запустить код примеров.

Конфигурация IntelliJ IDEA

Возможности Java 15 поддерживаются в IntelliJ IDEA с версии 2020.2, выпущенной в июле 2020 года. Для настройки использования Java 15 выберите в свойствах проекта и модулей в параметре "Project SDK" значение "15", а в "Project language level" "15 (Preview) Sealed types, records, patterns, local enums and interfaces".

Также вы можете скачать Java 15 непосредственно из IntelliJ IDEA. Для этого в левой части окна "Project Structure" в разделе "Platform Settings" выберите "SDKs", затем нажмите вверху значок "+" и выберите "Download JDK". Укажите поставщика (Vendor), версию (Version) и каталог для загрузки JDK.

Возвращаемся к обработке подтипов Plant в классе Gardener

При создании sealed-иерархии вы знаете полный список наследников и вам не нужно обрабатывать какие-то общие случаи. Ветка else в методе process() класса Gardener никогда не будет выполнена. Однако нам все-равно нужно оставить else из-за return.

Сопоставление с образцом, добавленное в Java 14 для instanceof, может появиться в будущих версиях Java и в выражениях switch. С помощью улучшенного switch можно работать с полным списком наследников. Это позволит исключить написание любого "обобщенного кода" для обработки ситуаций, когда передается непредусмотренный подтип Plant:

// Этот код не работает в Java 15.// Он будет работать в будущих версиях Java // после реализации type-test-pattern в switch int processInAFutureJavaVersion(Plant plant) {   return switch (plant) {       case Cucumber c -> c.harvestCucumber();       case Climber cl -> cl.sowClimber();       case Herb h -> h.sellHerb();       case Shrub s -> s.pruneShrub();   }}

Пакеты и модули

Sealed-классы и их реализации должны находиться в одном модуле. Если базовый sealed-класс определен в именованном модуле, то все его реализации должны быть определены там же. Но они могут быть в разных пакетах.

Для sealed-класса, определенного в безымянном модуле, все его реализации должны быть в одном пакете.

Правила для базовых классов и наследников классов

Классы, расширяющие sealed-класс, должны быть объявлены как final, non-sealed или sealed. Модификатор final запрещает дальнейшее расширение, non-sealed позволяет другим классам расширять его, а sealed-подкласс должен следовать тем же правилам, что и родительский базовый класс необходимо явно указать список классов, которые могут его расширять.

Sealed-класс также может быть абстрактным. Его наследники могут быть как абстрактными, так и конкретными классами.

Давайте изменим набор классов, используемый в предыдущем разделе, и определим класс Plant как абстрактный с абстрактным методом grow(). Поскольку производный класс Herb является final-классом, то в нем должна быть реализация метода grow(). Non-sealed класс Shrub объявлен абстрактным и может не реализовывать метод grow(). Sealed-класс Climber реализует абстрактный метод grow():

Вот измененный код:

sealed abstract public class Plant permits Herb, Shrub, Climber {   abstract void grow();}final class Herb extends Plant {   @Override   void grow() {   }}non-sealed abstract class Shrub extends Plant {}sealed class Climber extends Plant permits Cucumber{   @Override   void grow() {   }}final class Cucumber extends Climber {}

Если вы определяете sealed-класс и его наследников в одном файле исходного кода, то можно опустить модификатор permits и имена подклассов, указанных в объявлении sealed-класса. В этом случае компилятор способен самостоятельно вывести иерархию.

Sealed-интерфейсы

Sealed-интерфейс позволяет явно указать интерфейсы, которые могут его расширять, и классы (включая записи), которые могут его реализовать. Для интерфейсов применяются правила, аналогичные sealed-классам.

Однако поскольку вы не можете объявить интерфейс с помощью модификатора final (иначе это противоречило бы его назначению, так как интерфейсы должны быть реализованы) интерфейс может быть объявлен только с использованием модификаторов sealed или non-sealed. В разделе permits перечисляются классы, которые непосредственно могут реализовать sealed-интерфейс, и интерфейсы, которые могут его расширять. Реализующий класс может быть final, sealed или non-sealed. Поскольку записи, появившиеся в Java 14, неявно являются final, то они не нуждаются в каких-либо дополнительных модификаторах:

sealed public interface Move permits Athlete, Person, Jump, Kick {}final class Athlete implements Move {}record Person(String name, int age) implements Move {}non-sealed interface Jump extends Move {}sealed interface Kick extends Move permits Karate {}final class Karate implements Kick {}

Давайте перейдем к следующему нововведению Java 15 локальные записи (record).

Записи (records)

Записи (records) предназначены для компактной записи объектов-значений (value object). Первое превью записей появилось в Java 14, а в Java 15 второе превью с некоторыми изменениями.

Если вы не знакомы с записями или хотите узнать об их поддержке в IntelliJ IDEA, то обратитесь к статье Java 14 и IntelliJ IDEA. В IntelliJ IDEA есть множество функций, которые помогут вам создавать и использовать записи.

В этом посте я расскажу об изменениях в Java 15 по сравнению с Java 14.

Java 15 позволяет определять локальные записи внутри метода. В следующем примере метод getTopPerformingStocks() ищет акции (stock), которые имеют наибольшую стоимость на указанную дату, и возвращает их названия.

List<String> getTopPerformingStocks(List<Stock> allStocks, LocalDate date) {   // TopStock - локальная запись (Record)   record TopStock(Stock stock, double stockValue) {}   return allStocks.stream()              .map(s -> new TopStock(s, getStockValue(s, date)))              .sorted((s1, s2) -> Double.compare(s1.stockValue(), s2.stockValue()))              .limit(2)              .map(s -> s.stock.getName())              .collect(Collectors.toList());}

Локальные интерфейсы и перечисления

Java 15 также позволяет объявлять локальные перечисления и интерфейсы. Внутри метода можно инкапсулировать локальные для него данные или бизнес-логику.

public void createLocalInterface() {   interface LocalInterface {       void aMethod();   }   // Код, использующий LocalInterface}public void createLocalEnum() {   enum Color {RED, YELLOW, BLUE}   // Код, использующий enum Color}

Однако в этих случаях нельзя использовать контекстные переменные. Например, для создания значений перечисления FOO и BAR нельзя использовать параметры метода:

void test(int input) {   enum Data {       FOO(input), BAR(input*2); // Ошибка. Нельзя обращаться к input       private final int i;       Data(int i) {           this.i = i;       }   }}

Сопоставление с образцом (pattern matching) для instanceof

Многие Java-разработчики используют оператор instanceof для сравнения типов. Если результат сравнения будет true, то далее следует явное приведение к типу, с которым сравнивали. Этот паттерн применяется довольно часто и выглядит следующим образом: сравнение - ifTrue - приведениеКТипу.

В Java 14 оператор instanceof стал проще за счет поддержки в нем сопоставления с образцом. Дополнительные переменные и явное приведение больше не нужны, что делает ваш код безопаснее и лаконичнее.

Это уже второе превью сопоставления с образцом для instanceof (изменений по сравнению с Java 14 нет).

Подробнее узнать о поддержке этой функциональности в IntelliJ IDEA вы можете в статье Java 14 и IntelliJ IDEA. Там также есть несколько интересных примеров того рефакторинга кода с помощью этой возможности и других инспекций из IntelliJ IDEA, таких как объединение вложенных if и извлечение или инлайнинг переменных.

Текстовые блоки

Многострочные строки и текстовые блоки были добавлены в Java 15 как стандартная языковая конструкция без каких-либо изменений по сравнению с Java 14.

Подробнее о поддержке текстовых блоков в IntelliJ IDEA вы можете узнать в статье Java 14 и IntelliJ IDEA.

Превью

Sealed-классы и интерфейсы появились в Java 15 в качестве превью. С новым релизным циклом в шесть месяцев новые языковые конструкции выпускаются в режиме превью. И в дальнейшем они могут появиться повторно в более поздних версиях в качестве второго или третьего превью с изменениями или без них. Как только они станут достаточно стабильными, они могут быть добавлены в стандарт языка.

Превью версии являются полноценными, но могут измениться, что, по сути, означает готовность функциональности к использованию разработчиками, но ее детали могут поменяться в будущих релизах Java в зависимости от отзывов. В отличие от API, языковые конструкции не могут в будущем быть объявлены устаревшими (deprecated). Если вы хотите высказать свое мнение о каких-либо превью возможностях, то можете сделать это в списке рассылки JDK (требуется бесплатная регистрация).

По указанной выше причине IntelliJ IDEA поддерживает превью возможности Java только для текущего JDK. Реализация превью возможностей может измениться от версии к версии, пока они не будут удалены или добавлены в качестве стандарта. Код, использующий превью возможности из более ранней версии Java SE, может не компилироваться или не запускаться в новой версии. Например, Switch Expressions в Java 12 использовали break для возврата значения из ветки, а позже это было изменено на yield. Поддержка использования break для возврата значения из Switch Expressions уже отсутствует в IntelliJ IDEA.

Резюме

IntelliJ IDEA стремится не только к поддержке новых функций Java, но и к разработке для них новых проверок и инспекций.

IntelliJ IDEA 2020.2 поддерживает все новые языковые конструкции Java 15. Попробуйте уже сегодня sealed-классы и интерфейсы, а также записи (record), сопоставление с образцом (pattern matching) для instanceof и текстовые блоки (text block). Скачать IntelliJ IDEA вы можете по этой ссылке.

Мы всегда рады обратной связи от наших пользователей. Не забудьте оставить отзыв о поддержке этих возможностей в IntelliJ IDEA.


А прямо сейчас в OTUS открыт набор на новый поток курса Java Developer. Professional. Приглашаю всех желающих на demo day курса, в рамках которого можно будет подробно ознакомиться с программой и процессом обучения, а также задать вопросы экспертам OTUS.

Подробнее..

JPoint 2021 тенденции и тренды мира Java

19.04.2021 00:19:54 | Автор: admin
В третьем онлайн-сезоне конференций, проводимых JUG Ru Group, с 13 по 17 апреля 2021 года успешно прошла Java-конференция JPoint 2021.



Что было интересного на конференции? Какой тематики были доклады? Кто из спикеров и про что рассказывал? Что изменилось в организации конференции и долго ли ждать возвращение офлайн-формата? Можно ли что-то ещё придумать оригинальное при написании обзора о конференции?

Основным инструментом, который поможет делать рассказ о конференции и её докладах в этом обзоре, будет облако тегов (англ. tag cloud). Облако тегов визуальное представление текстовых данных, в котором слова, используемые с большей частотой, выделяются размером шрифта или цветом. Облака тегов помогут

  • показать общие тенденции, тренды, направленность прошедшей конференции;
  • проиллюстрировать каждый из представленных в обзоре докладов;
  • поиграть в игру, где будут и облака тегов, и доклады, и спикеры.

Картинка в начале статьи с облаком тегов в форме логотипа JPoint была сформирована с использованием названий и описаний абсолютно всех докладов конференции. Посмотреть файл в оригинальном размере можно по следующей ссылке. Из 1685 слов в топ-3 с заметным отрывом попали: Spring (50 повторений), Java (49 повторений) и data (21 повторение). Из прочих фаворитов, но с меньшей частотой использования, можно также отметить слова session, JDK, cloud, code, Kubernetes, GraphQL и threads. Данная информация помогает понять, куда движется Java-платформа и названия каких сущностей, технологий и продуктов являются самыми актуальными сегодня.

Открытие


Долгожданное открытие конференции выполнили Алексей Фёдоров, Глеб Смирнов, Андрей Когунь и Иван Углянский. Ими были представлены спикеры, эксперты и программный комитет все те, без которых проведение конференции было бы невозможным.



Редкий случай, когда можно было видеть одновременно трёх лидеров Java-сообществ: JUG.ru (Алексей Фёдоров), JUG.MSK (Андрей Когунь) и JUGNsk (Иван Углянский).



Основные события конференции были трёх типов:

  • доклады;
  • мини-доклады партнёров;
  • воркшопы.

Доклады


Приятной неожиданностью для русскоязычных участников конференции стало то, что Себастьян Дашнер свой доклад Качественный код в тестах не просто приятный бонус делал на русском языке. Себастьян принимает участие в качестве спикера в конференциях JUG Ru Group c 2017 года, причём, не только в Java-конференциях. Текущий доклад был посвящён интеграционному тестированию, поэтому в нём присутствовали и Java, и тесты на JUnit, и Docker. В качестве приглашённого эксперта рассказ успешно дополнил Андрей Солнцев. Отличное знание русского языка и неизменно интересный доклад от Себастьяна Дашнера.





В докладе Building scalable microservices for Java using Helidon and Coherence CE от Дмитрия Александрова и Aleksandar Seovic было продемонстрировано совместное использование двух продуктов компании Oracle Helidon (в его разработке участвует Дмитрий) и Oracle Coherence (Aleks является архитектором продукта). Митя ранее делал доклады про MicroProfile (первый доклад и второй) и написал хорошую статью на Хабре про Helidon, поэтому было любопытно посмотреть дальнейшее развитие темы. Повествование сопровождалось демонстрацией кода и запуском приложения, код которого доступен на GitHub. Докладчики, каждый из которых лучше знаком со своим продуктом, отлично дополняли друг друга. Посмотреть оказалось увлекательно и полезно.





Анна Козлова работает над созданием нашего любимого инструмента IntelliJ IDEA, внеся по количеству коммитов самый большой вклад среди всех конрибьютеров в репозиторий IntelliJ IDEA Community Edition, что вызывает огромное уважение.

В своём докладе Многоступенчатые рефакторинги в IntelliJ IDEA Анна очень доходчиво и убедительно показала, как сложные типы рефакторингов могут быть получены комбинацией более простых уже существующих рефакторингов. В препарировании рефакторингов ей ассистировал коллега по компании JetBrains Тагир Валеев. Исключительно полезен как сам доклад (определённо, стоит его пересмотреть), так и озвученная Анной и Тагиром статистика применения разного типа рефакторингов пользователями.





Type inference: Friend or foe? от Venkat Subramaniam. Венкат фантастически харизматичный спикер, которого каждый раз хочется смотреть при присутствии его докладов на конференции. Мне кажется, ценность его докладов в том числе в том, что он заставляет увидеть другую сторону каких-то вещей, ранее казавшимися простыми и очевидными. В этот раз подобной темой было выведение типов (type inference). Кроме интересной информации в очень экспрессивном исполнении наконец-то узнал, в чём Венкат показывает презентации и запускает код (ломал голову над этим при просмотре его предыдущих докладов) это редактор vi.





Доклад Антона Кекса про то, Что такое Работающий Продукт и как его делать своеобразное продолжение его выступления The world needs full-stack craftsmen двухлетней давности. Если в прошлом докладе Антон говорил о недопустимости узкой специализации разработчика, то в этот раз сфокусировал своё внимание и внимание слушателей на том, почему важно и как можно сделать качественный работающий программный продукт. Докладчик подкреплял приведённые теоретические тезисы практическими примерами, поэтому просмотр стал весьма захватывающим зрелищем.





Под доклад Spring Data Рostроитель (Spark it!) в исполнении Евгения Борисова предусмотрительно был отведён отдельный пятый день конференции. Планировалось, что демонстрация написания поддержки Spark для Spring Data займёт 6 часов (в итоге вышло почти 7). Положительным моментом онлайн-конференции является то, что в случае длинных докладов можно комфортно прерывать и продолжать просмотр позднее. Много кода, новой информации и подробных пояснений. Традиционно получилось качественно, основательно и увлекательно.



Мини-доклады партнёров


+10 к безопасности кода на Java за 10 минут стал первым из 15-минутных докладов партнёров, увиденных на конференции. Алексей Бабенко сконцентрировал в небольшом времени, отведённом на доклад, внимание на вопросах безопасности при написании кода на языке Java. Формат мини-докладов, которые показываются в перерывах между большими докладами, оказался достаточно удачным и востребованным.





Ещё один мини-доклад, 1000 и 1 способ сесть на мель в Spring WebFlux при написании высоконагруженного сервиса от Анатолия Тараканова, может пригодиться в том случае, если используете Spring WebFlux и возникли какие-либо проблемы в разработке и эксплуатации приложения. Краткое перечисление проблем и способов их решений может чем-то помочь.





В кратком докладе R2DBC. Стоит ли игра свеч? от Антона Котова даётся оценка практической применимости спецификации R2DBC в текущий момент. После ранее прослушанного вот этого доклада Олега Докуки было интересно узнать сегодняшнее положение вещей. Антон в конце доклада даёт однозначный ответ на вопрос Стоит ли игра свеч?. Через некоторое время ответ должен, вероятно, измениться.





Доклад Секретный ингредиент: Как увеличить базу пользователей в 3 раза за год в исполнении Александра Белокрылова и Алисы Дрожжиновой представил следующие новости от компании BellSoft, наиболее известной своим продуктом Liberica JDK:

  • клиентская база компании увеличилась в 3 раза за последний год;
  • появился новый инструмент Liberica Administration Center (LAC) для централизованного обновления Java на компьютерах пользователей;
  • стала доступна утилита Liberica Native Image Kit на базе GraalVM CE;
  • компания ведёт работы в области серверов приложений (на сайте доступен продукт LiberCat на основе Apache Tomcat).





Кирилл Скрыган докладом Code With Me новая платформа для удаленной коллаборативной разработки представил новую возможность продуктов компании JetBrains для парного программирования и коллективной разработки в среде разработки. Была показана базовая функциональность сервиса и перечислены получаемые преимущества.



Воркшопы


На конференции было два воркшопа: Парное программирование вместе с Андреем Солнцевым и Антоном Кексом и Строим Бомбермена с RSocket вместе с Сергеем Целовальниковым и Олегом Докукой. Для просмотра во время конференции выбор пал на воркшоп Строим Бомбермена с RSocket. Олег и Сергей убедительно продемонстрировали на примере игры взаимодействие составных частей приложения по протоколу RSocket. Код приложения доступен на GitHub для изучения и повторения действий, выполненных во время воркшопа.



Конференции, Java-митапы и игра


На сайте jugspeakers.info по-прежнему доступно приложение, состоящее из двух частей:

  • поиск и просмотр информации о конференциях и Java-митапах, организуемых JUG Ru Group, JUG.MSK и JUGNsk (спикеры, доклады, презентации, видео, статистика);
  • игра Угадай спикера.

К новому сезону конференций в приложение были сделаны ряд доработок и новшеств, первое из которых позволит более эффективно использовать программу во время конференций:

  1. Начальная страница отображает теперь ближайшую или идущую в данный момент конференцию (картинка слева ниже сделана во время работы JPoint 2021). Установка значений всех фильтров поиска по умолчанию на текущую конференцию также должна помочь сделать программу максимально полезной во время идущей сейчас конференции.
  2. Приложение дополнено фильтрами по организатору конференций и митапов (средняя картинка).
  3. Добавлена информация о видео всех докладов, сделанных публично доступными (в том числе видео докладов с Joker 2020).
  4. Появились данные о конференции SnowOne, второй год проводящейся новосибирским Java-сообществом JUGNsk (см. картинку внизу справа).
  5. Стало возможным видеть учётные записи Хабра у спикеров.



Во второй части приложения (в игре Угадай спикера) появилось 2 новых режима: угадывание облака тегов по спикеру и спикера по облаку тегов. Облака тегов формируются на лету, при выборе пользователем конференций или митапов. Источником для создания облаков тегов по спикерам являются наименования и описания их докладов. Для каждого спикера создаётся одно (по тексту на английском языке) или два облака тегов (второе облако возможно, если у докладов есть описание и на русском языке). Переключением языка интерфейса можно посмотреть оба облака тегов.

Хорошее знание тематики, на которую спикеры предпочитают делать свои доклады, поможет Вам правильно выбрать из нескольких вариантов либо облако тегов, либо спикера.



Чем больше конференций выбрано в игре для данных режимов (угадывание облака тегов по спикеру и спикера по облаку тегов), тем больше информации для формирования облака тегов и результат будет более точный. Java-разработчикам для игры можно порекомендовать следующие варианты в порядке усложнения:

  1. Выбрать только одну конференцию JPoint 2021 (Тип события JPoint, Событие JPoint 2021)
  2. Выбрать все конференции JPoint (Тип события JPoint, Событие конференции за все годы)
  3. Выбрать все Java-события (Тип события Joker, JPoint, SnowOne, JBreak, JUG.MSK, JUG.ru и JUGNsk)

Код приложения находится на GitHub, репозиторию можно ставить звёздочки.

Закрытие


Алексей Фёдоров, Глеб Смирнов и Андрей Когунь закрыли конференцию, подведя итоги и поделившись каждый своими впечатлениями от пятидневного конференционного марафона.



Несмотря на вынужденное ограничение онлайн-форматом, конференция продолжает держать высокую планку: удобная платформа для просмотра докладов и взаимодействия с другими участниками, множество докладов по Java-технологиям, горячая информация о новых продуктах (Space и Code With Me), любимые спикеры с отлично дополняющими их приглашёнными экспертами.

В весенне-летнем сезоне онлайн-конференций JUG Ru Group ещё будут конференции HolyJS, DotNext (20-23 апреля 2021 года) и Hydra (15-18 июня 2021 года). Можно посетить любую из конференций отдельно или купить единый билет на все шесть конференций сезона (три уже прошедших и три оставшихся), видео докладов становятся доступными сразу же после завершения конференций.
Подробнее..

Хамелеон, которого мы создали и приручили

01.12.2020 14:07:50 | Автор: admin

Речь, конечно, пойдет не о харизматичных ящерках, а о нашем собственном инструменте автоматизированного тестирования. Представляем вашему вниманию кейс создания фреймворка Хамелеон и рассказ о его функциональности.


Его появлению предшествовало 15 лет практики тестирования в компании IBS AppLine* (лидера российского рынка аутсорсинга услуг тестирования по версии TAdviser за 2018 год на минуточку!). На базе этих знаний и экспертизы мы задались целью ускорить старт проектов, повысить качество тестирования, упростить введение в работу новичков. Решение должно позволить автоматизировать функциональное тестирование веб, мобильных, десктоп-приложений и различных видов API.




В общем, исследовательский центр IBS AppLine Innovation** суммировал весь опыт компании и создал Хамелеон инструмент для автоматизации функционального тестирования. Делался с использованием языка программирования Java и инструментов Cucucmber, Selenium, Appium, Winium, Spring. Этот фреймворк:


  • позволяет сэкономить до 30% времени разработки и сопровождения тестов;
  • снижает риск ошибок за счет автоматического заполнения параметров этапов теста;
  • помогает обучать и привлекать к разработке тестов стажеров, владеющих только базовыми навыками программирования;
  • при переходе с проекта на проект дает возможность использовать одну и ту же библиотеку шагов, а также единый подход к написанию тестов;
  • может работать с экзотическими компонентами тестируемых систем благодаря удобству подключения дополнительных расширений.

Теперь подробнее о функционале



Как устроен Хамелеон


Вот несколько особенностей нашего фреймворка:


  • Это многомодульный maven-проект, который включает модули тестирования web, мобильных приложений, SAP-приложений, баз данных, Rest API и web-сервисов. Необходимые модули подключаются к проекту.
  • Мы взяли проверенные временем оpen source-инструменты, в том числе Selenium, Appium, Winium, и удачно их объединили в одном решении.
  • Для ускорения разработки автоматизированных тестов мы создали плагин для среды разработки IntelliJ IDEA. Получился полезный инструмент разработчика автоматизированных тестов. Плагин дополняет возможности IDEA, делая ее полноценным рабочим местом.
  • Для удобства разработки автоматизированных тестов для web-приложений мы создали расширение для браузера Google Chrome, которое позволяет добавлять элементы тестируемого приложения в проект прямо из браузера и имеет возможность записи автоматизированного теста в формате Cucumber методом Record&Playback.

Open source-библиотеки


В основе инструмента лежат оpen source-библиотеки Selenium, Appium, Winium, UIAutomation. Для разработки самих тестов используется фреймворк Cucumber, который позволяет писать на русском языке. Такой формат понятен и ручным тестировщикам, и не имеющим отношения к написанию конкретного теста специалистам автоматического тестирования, что снижает порог вхождения сотрудников в проект. Всему, что происходит в Cucumber, соответствуют свои Java-команды, так что при необходимости тесты можно разрабатывать на чистой Java.



Простота установки


Для разработки автоматизированных тестов с использованием Java на рабочую станцию устанавливаются Java JDK, Apache Maven/Gradle, IntelliJ IDEA, плагины для Intellij IDEA, Git Client. У начинающих специалистов это занимает много времени. Мы упростили процесс, разработав общий инсталлятор, который представляет собой .exe-файл с возможностью выбора необходимого ПО для установки на рабочее место:




Начало разработки


Для разработки автоматизированных тестов можно использовать готовые стартеры проектов. Стартеры это архетипы maven, которые содержат готовую структуру проекта. Они хранятся во внутреннем репозитории компании. При создании проекта в IntelliJ IDEA нужно лишь выбрать необходимые. Например, для разработки тестов, которые взаимодействуют с web-приложением и REST, необходимо подключить модули chameleon-selenium-cucumber и chameleon-rest-cucumber.




Немного о фреймворке


В основном автоматизированные тесты разрабатываются с помощью инструмента Cucumber, который позволяет писать тесты на русском языке. Автоматизированный тест состоит из следующих блоков:


  • переменные для тестовых сценариев;
  • наименование тестируемой функциональности;
  • тестовые сценарии, которые состоят из последовательных шагов действий с тестируемым приложением.

Пример автоматизированного теста:



# language: ru# Тестовые данные:  # $ФИО Иванов Иван Иванович  # $ссылка https://www.appline.ru/Функция: Заявка на обучение  Сценарий: Заявка на обучение    * страница "Главная" загружена    * выбран элемент коллекции "Меню" с параметрами:      | field        | operator | value             |      | Наименование | равно    | Start IBS AppLine |    * нажатием на кнопку "Наименование" загружена страница "Start IBS AppLine"    * поле "Имя" заполняется значением "#{ФИО}"    * поле "Ссылка на резюме" заполняется значением "#{ссылка}"    * выполнено нажатие на "Отправить"    * поле "Required field" видимо

Существуют шаблоны для работы с переменными: операции с датами, математические операции, выполнение кода и т.д. Например, для работы с текущей датой используется шаблон #now{дата; исходный_формат; смещение}. Предположим, в автоматизированном тесте необходимо проверить дату операции, которая была только что осуществлена. Такая проверка будет выглядеть так:


* значение поля "Дата операции" равно "#now{dd.MM.yyyy HH:mm}"

А, например, создать отложенную операцию, которая исполнится завтра:


* поле "Дата операции" заполняется значением "#now{dd.MM.yyyy;+1d}}"

Выполнить программный код можно с использованием шаблона #script{RESULT=выражение_java}. Например, удаление лишних символов в переменной будет выглядеть следующим образом:


* в переменной "Номер_счета" сохранено значение поля "Номер счета"* значение поля "Номер счета" равно "#script{RESULT = Номер_счета.replaceAll("", "")}"

В нашем фреймворке разработаны основные Cucumber-шаги, которые напоминают шаги из ручных тестовых сценариев. Они описывают действия, которые человек может совершить с тестируемым приложением: нажать на элемент, ввести значение в поле, прочитать значение, сравнить значения и т.д. Таких шагов примерно 30. Для часто повторяемых операций такие шаги объединяются в более крупный шаг.


Например, авторизация в приложении может описываться в 3 шага:


Когда заполняются поля:  | field  | value        |  | Логин  | test@test.ru |  | Пароль | 123123       |И выполнено нажатие на "Войти"Тогда страница "Главная" загружена

Или на основе этих шагов создается 1 шаг (пароль в этом случае хранится в отдельном файле с пользователями):


Дано авторизован пользователь "test@test.ru"

Все размеченные элементы тестируемого приложения имеют свой тип, например, Button, TextInput, Combobox, Checkbox и т.д., это позволяет использовать одни и те же Cucumber-шаги для работы с разными типами элементов. Например, есть шаг:


* поле "field" заполяется значением "value"

Он сможет работать как c текстовыми полями, так и чекбоксами, выпадающими списками и т.д. Это позволяет при разработке автоматизированного теста не задумываться о заполнении полей этим занимается сам фреймворк, мы просто указываем ему заполнить поле.



АРМ тестировщика или разработка теста


Стандартное представление проекта для разработки тестов содержит много классов и вспомогательных файлов, которые очень редко нужны при разработке самого автоматизированного теста. Чтобы упростить этот процесс мы постарались оградить тестировщика от этой информации и разработали плагин для Intellij IDEA. Его интерфейс стал рабочим местом тестировщика, и содержит следующие разделы:


  • список тестов,
  • объектный репозиторий,
  • список доступных действий,
  • настройки запуска,
  • документацию,
  • различные автокомплиты, ускоряющие процесс создания теста.


Список тестов


Список тестов представляет собой привычную иерархию папок и файлов. На вкладке плагина можно создать новый тест, при этом будет загружен шаблон, редактирующийся в настройках проекта.




Репозиторий объектов


Традиционный и очень популярный инструмент разработчика автоматизированных тестов шаблон Page Object Pattern. При его использовании элементы тестируемого приложения описываются в отдельных java-классах, которые соответствуют страницам тестируемого приложения. На крупных проектах таких Page Object-классов становится очень много. Кроме того, иногда они еще и называются беспорядочно. В результате для больших проектов такой подход не очень удобен: неизбежно дублирование работы на проектах работает много специалистов, приходится расшифровывать работу коллег и анализировать разметку элементов, а это не всегда проходит гладко.


Поэтому мы создали репозиторий объектов тестируемого приложения. Он хранит все его элементы не в классах, а в одном или нескольких xml-файлах, а плагин для IntelliJ Idea показывает эти xml-файлы в виде древовидной структуры, в которой узлами являются страницы тестируемого приложения. Вместо разномастных английских названий мы используем русский язык, что упрощает работу тестировщиков, так им легче находить размеченные страницы и элементы, а также добавлять новые.


Приведем конкретный пример. Для одного крупнейшего отечественного банка (название по понятным причинам не называем) нами было разработано 20 автотестов, использовавших 78 страниц тестируемых приложений (достаточно длинный бизнес-процесс). В обычных условиях тестировщикам пришлось бы создать 78 java-классов и разметить на них более 2000 элементов. С Хамелеоном мы всю эту громаду открываем в древовидной структуре, в которой их легко и просто просматривать, перетаскивать, компоновать.




Существует два способа добавления элементов в репозиторий.


Добавление в среде разработки IntelliJ IDEA:



Интерфейс мы постарались сделать максимально удобным: из него можно добавлять, редактировать и удалять элементы, есть несколько видов сортировок и поиска элементов в дереве. При добавлении элемента необходимо указать его тип, наименование и локатор.


Также есть возможность добавлять элементы прямо из тестируемого приложения с помощью созданного нами расширения для браузера Google Chrome. Расширение самостоятельно определяет тип элемента, его наименование и подставляет локатор. Для этого достаточно навести мышкой на элемент. Расширение синхронизируется с плагином в IntelliJ IDEA, поэтому все, что происходит в браузере, передается в среду разработки. Так можно наполнять репозиторий объектов, проходя ручной тест в браузере. С помощью расширения можно проверить корректность локатора уже существующего в репозитории элемента.




Чтобы не было дублирования элементов, существует тип элемента Block. Например, у нас в тестируемом приложении есть блок Меню, и он доступен на всех страницах приложения: в этом случае в репозитории элементов мы размечаем его как страницу Меню, содержащую элементы меню, а на других страницах добавляем его как элемент с типом Block. Таким образом, получается переиспользование элементов, которые встречаются более чем на одной странице.



Разработка автоматизированного теста


Автотест состоит из последовательности шагов, имитирующих действия пользователя (нажатия на кнопку, заполнение поля). Все эти шаги уже есть во фреймворке, так что тестировщик может просто собрать тест из готовых кубиков.


При разработке автотеста можно начать вводить наименование шага с клавиатуры и воспользоваться подсказкой для его подстановки, а также использовать библиотеку шагов, которая отображается в плагине.


При этом доступны подсказки не только для списка возможных шагов, но и для элементов, которые могут участвовать в выбранном действии. Плагин определяет страницу (контекст) и предлагает элементы из репозитория объектов или переменные для сucumber-шага, которые относятся к выбранной странице. Это исключает возможность ошибки при использовании еще не размеченного элемента страницы и ускоряет процесс разработки теста.




Второй способ разработки автоматизированного теста использование репозитория шагов. В плагине в древовидной структуре показываются шаги, которые можно перетащить методом Drag&Drop, как показано ниже. При использовании такого варианта создания автоматизированного теста тоже присутствуют подсказки элементов и переменных. Таким вариантом обычно пользуются начинающие сотрудники, которым проще искать необходимые шаги в репозитории, а не java-классах.




Третий способ разработки автотеста (самый удобный и популярный) с помощью нашего расширения для браузера Google Chrome. Плагин записывает действия пользователя на странице тестируемого приложения и конвертирует их в автоматизированный тест.


Например, пользователь логинится на сайте, нажимая для этого на кнопку Логин. Рекордер записывает шаг * выполнено нажатие на поле Логин. Пользователь заполняет поле Логин значением User, рекордер записывает шаг * поле Логин заполняется значением User и так далее. После этого получившийся автотест можно скопировать и вставить в среду разработки для редактирования. Плагин автоматически, на основе объектного репозитория, определяет, на какой странице находится пользователь, и выполняет поиск размеченных элементов. Если элемента в репозитории нет, то будет предложено его добавить.


Пока рекордер существует только для браузера. В процессе разработки находится рекордер для SAP GUI.



Запуск и отладка


Обычно при запуске тестов необходимо создать в среде разработки конфигурацию запуска. Плагин позволяет запускать и отлаживать разработанные тесты нажатием правой кнопки мышки на тесте в списке тестов, а сама конфигурация запуска определяется на основе выбранных параметров на вкладке плагина Настройки. Из коробки можно выбрать:


  • подсветку элементов будут выделяться элементы, с которыми взаимодействует тест;
  • запись видео в случае ошибки к отчету будет прикреплено видео прохождения автоматизированного теста;
  • конфигурацию запуска браузер, в котором будет выполнен тест;
  • режим запуска с графическим интерфейсом или headless-режиме (без графического интерфейса);
  • закрытие браузера после теста удобно при разработке длинных тестов или локализации ошибок;
  • подключение к открытому браузеру чтобы начать автоматизированный тест не с начала, а с выбранного места, подключаясь к открытому браузеру.



Selenium не позволяет начать автоматизированный тест с произвольного места и на каждый запуск создает новую сессию браузера. Для экономии времени мы реализовали возможность начинать тест не сначала, а с любого момента: для Selenium был разработан механизм подключения к уже открытой сессии браузера.


Чтобы отслеживать ход выполнения теста, мы разработали консоль, куда перенаправляется весь лог с IntelliJ IDEA. Это позволяет контролировать процесс и понимать, что сейчас выполняет автоматизированный тест. Консоль отображается поверх содержимого экрана и не мешает выполнению теста. Есть возможность настраивать формат вывода в консоль (размер, шрифт, цвет и т.д.).




Документация


Плагин содержит документацию по работе с фреймворком, настройками запуска тестов, частые вопросы и т.д. Документация обновляется по мере разработки новой функциональности. Наличие документации в среде разработки позволяет разработчику тестов решить проблему, не покидая среды разработки.




Тестирование API


Для тестирования API (REST и SOAP) также используется объектный репозиторий. В этом случае страницами будут endpoint, а элементами заголовки и тело запроса.




Во фреймворке реализованы базовые действия с API, при наборе действий происходит автоподстановка параметров вызова.




Отчет о выполнении автоматизированных тестов


В качестве инструмента отчетности был выбран Allure. При запуске тестов из среды разработки IntelliJ IDEA аllure-отчет можно открыть прямо из плагина.


Если использовать классическое подключение Allure к проекту, то отчет о прохождении автоматизированных тестов строится после выполнения всех тестов. Такой подход не очень удобен при запуске большого количества автоматизированных тестов, поэтому мы создали отдельный сервис, который строит и перестраивает отчет в процессе всего запуска, а также показывает статистику очереди тестов на выполнение. Таким образом, мы получаем информацию о дефектах тестирования в режиме реального времени и можем вовремя среагировать.



Результаты


Хамелеон помог нам не на словах, а на деле.


Во-первых, с ним старт проектов стал гораздо быстрее. Ранее на него могло уйти до трех недель, теперь же обычный срок два дня.


Во-вторых, унификация подхода позволила минимизировать время переключения тестировщика при переходе на другой проект.


В-третьих, теперь мы привлекаем ручных теcтировщиков для написания автотестов. Временные затраты на их обучение минимальны, а этапы разработки системы автоматизации функционального тестирования это удешевляет.


Наконец, мы получили еще одно преимущество на отечественном рынке: кодирование на русском языке легче освоить. Для этого у нас есть корпоративный университет, где мы обучаем будущих тестировщиков писать автотесты с нуля буквально за месяц. И в этом очень важную роль играет Хамелеон: с его помощью мы закладываем общую базу, на нем обязательно проводим несколько занятий по разработке тестов. Ежемесячно через этот курс проходит 5-10 человек, имеющих базовые знания языка программирования Java.



Планы на будущее


На данный момент команда разработки трудится над инструментом управления тестированием Кайман, который объединит в себе ручное, автоматизированное и нагрузочное тестирование. Отдельная команда выделена на изучение технологии машинного зрения, что в дальнейшем должно позволить отказаться от локаторов элементов и разрабатывать автоматизированные тесты для приложений, где стандартными средствами распознавание элементов невозможно. Об этих инструментах мы расскажем в следующих наших постах.





* IBS AppLine лидер российского рынка услуг тестирования и обеспечения качества программного обеспечения, разработчик решений для автоматизации тестирования. Компания была создана в 2001 году, в штате более 700 специалистов, общее количество проектов превысило 1500.


** IBS AppLine Innovation (ранее Аплана АйТи Инновации) была создана в декабре 2017 года как центр исследований и разработки компании IBS AppLine (ранее Аплана). Основу команды составили ее собственные опытные инженеры и разработчики.

Подробнее..

Из песочницы Разработка базы данных медиаплеера для организации музыки типа iTunes

31.08.2020 12:09:59 | Автор: admin

Описание предметной области


Предметной областью является медиаплеер, аналог iTunes. Основными сущностями в данной базе данных выступает пользователь и сборник композиций.

Каждая композиция принадлежит только одному исполнителю и одному альбому, но добавлять их может множество пользователей. Каждый пользователь может добавлять неограниченное количество композиций.

Каждый пользователь должен иметь уникальную почту и пароль, который может повторяться, благодаря эти сведениям пользователь может зайти в свой аккаунт. Также для пользователей обязательными при регистрации являются следующие поля: имя, номер телефона, страна проживания, дата старта подписки и дата окончания подписки. После правильного входа или успешной регистрации пользователь попадает на основную страницу, где пользователю представляется подборка трэков разных исполнителей, среди которых он может выбирать понравившиеся ему и добавлять их в свой плейлист. Количество добавляемых трэков не ограничено. Каждый пользователь имеет свой собственный личный кабинет, в котором он может просматривать добавленную музыку, удалять её, а также имеется опция просмотра личных данных пользователя, введенных им при регистрации. Пользователь в любой момент может удалить свой аккаунт вместе со всем его содержимым и своими данными из системы.

Построение логической модели


Используя описание предметной области, построили логическую модель

image

Note
Для разрешения отношение многие-ко-многим между сущностями USERS и TRACK LIST была создана таблица SUBSCRIPTION VALIDITY, которая включает в себя дату начала подписки и её конец.

Остальные сущности отображают сущности нашей предметной области.


Построение реляционной модели


Логическая модель была переведена в реляционную с использованием Oracle SQL Developer Data Modeler

image

Соответствие нормальным формам


Все таблицы данной модели соответствуют 1НФ, потому что все атрибуты имеют только единственное значение для каждой записи, что исключает множественность.

Все таблицы находятся во второй нормальной форме, так как каждый не ключевой атрибут неприводимо зависит от первичного ключа.

Все таблицы находятся в третьей нормальной форме, так как каждый не ключевой атрибут нетранзитивно зависит от первичного ключа. Не существует зависимости неключевого атрибута от другого неключевого атрибута.

Все таблицы соответствуют НФБК, потому что детерминант всех функциональных зависимостей являются потенциальными ключами.

Пояснения функциональных зависимостей
image


Создание базы данных и приложения


База данных создается на локальном сервере phpMyAdmin. Результат работы программы представлен ниже:

image

Доступные страницы
Стартовое окно/ Окно входа

image

Основная страница приложения

image

Окно регистрации нового пользователя

image

Окно персональных данных пользователя

image

Личный кабинет пользователя

image


Тестирование программы


Проверка реакции приложения на ожидаемые входные данные и тестирование работоспособности кнопок


Проверим правильность реакции приложения на ожидаемые входные данные + работоспособность всех полей, кнопок и выпадающих списков.

Проверка корректности регистрации пользователя
image

Доказательство корректного создания нового пользователя

image

Проверка правильности работы кнопки добавления музыки в медиатеку пользователя
Добавление композиции FALSE ALARM by THE WEEKND в плейлист пользователя

image

Доказательство добавления композиции FALSE ALARM by THE WEEKND в плейлист пользователя

image

Композиция FALSE ALARM by THE WEEKND в БД TRACK LIST соответствует номеру 32

image

Как видно из таблицы MY MUSIC, композиция с номером 32 действительно добавилась в плэйлист пользователя

image

Проверка правильности работы кнопки удаления музыки из медиатеки пользователя
Удаление композиции FALSE ALARM by THE WEEKND из плейлиста пользователя

image

Доказательство удаления композиции FALSE ALARM by THE WEEKND в плейлист пользователя

image

Как видно из таблицы MY MUSIC, композиция с номером 32 действительно удалилась из плэйлиста пользователя

image

При этом композиция 32 никуда не пропала из таблицы TRACK LIST

image

Проверка правильности работы кнопки сортировки музыки по исполнителям
Сортировка музыки по исполнителю MUSE

image

Сортировка музыки по исполнителю KASABIAN

image

Проверка правильности работы кнопки удаления аккаунта пользователя из системы
Создадим в системе пользователя ivanivanov@mail.ru

image

Перейдем во вкладку с персональными данными этого пользователя

image

Теперь удалим данного пользователя при помощи кнопки Delete user from system

image

Как видно из таблицы USERS, пользователь с номером 18 действительно удалился из списка зарегестрированых пользователей


Проверка реакции приложения на ошибочные входные данные


При вводе некорректных данных программа уведомляет об этом пользователя

Ошибка некорректного введения имени
image

Ошибка некорректного введения почты
image

Ошибка некорректного введения номера телефона
image

Ошибка возникающая при попытке продублировать учетные данные уже существующего пользователя
image

Ошибка возникающая при попытке введения учетных данных несуществующего пользователя
image


На ожидаемый ввод данных все работает. На отсутствие ввода и при неправильном вводе программа выдает предупреждение

Приложения


Приложение 1 код для создания таблиц


Код для создания таблиц в Oracle Data Modeler
-- Generated by Oracle SQL Developer Data Modeler 19.4.0.350.1424--   at:        2020-06-01 16:18:08 MSK--   site:      Oracle Database 11g--   type:      Oracle Database 11gCREATE TABLE albums (    id            VARCHAR2(5) NOT NULL,    album_name    VARCHAR2(20) NOT NULL,    release_year  DATE NOT NULL,    genre         VARCHAR2(20) NOT NULL);ALTER TABLE albums ADD CONSTRAINT albums_pk PRIMARY KEY ( id );CREATE TABLE my_music (    user_id    VARCHAR2(5) NOT NULL,    my_msc_id  NUMBER NOT NULL);CREATE UNIQUE INDEX my_music__idx ON    my_music (        user_id    ASC );ALTER TABLE my_music ADD CONSTRAINT my_msc_pk PRIMARY KEY ( my_msc_id );CREATE TABLE performers (    id              VARCHAR2(5) NOT NULL,    performer_name  VARCHAR2(30) NOT NULL);ALTER TABLE performers ADD CONSTRAINT performers_pk PRIMARY KEY ( id );CREATE TABLE subscription_validity (    "Start_Date/_Start_Time"  DATE NOT NULL,    "End_Date/_End_Time"      DATE NOT NULL,    user_id                   VARCHAR2(5) NOT NULL,    track_list_id             VARCHAR2(5) NOT NULL);ALTER TABLE subscription_validity ADD CONSTRAINT subscription_validity_pk PRIMARY KEY ( user_id,                                                                                        track_list_id );CREATE TABLE songs (    id                  VARCHAR2(5) NOT NULL,    track_name          VARCHAR2(50) NOT NULL,    genre               VARCHAR2(20) NOT NULL,    duration            TIMESTAMP NOT NULL,    albums_id           VARCHAR2(5) NOT NULL,    performers_id       VARCHAR2(5) NOT NULL,    my_music_my_msc_id  NUMBER NOT NULL);ALTER TABLE track_list ADD CONSTRAINT track_list_pk PRIMARY KEY ( id );CREATE TABLE "USER" (    id                  VARCHAR2(5) NOT NULL,    password            VARCHAR2(20) NOT NULL,    name                VARCHAR2(30) NOT NULL,    email               VARCHAR2(30) NOT NULL,    phone_number        VARCHAR2(12) NOT NULL,    country             VARCHAR2(20) NOT NULL,    balance             NUMBER(7, 2),    my_music_my_msc_id  NUMBER NOT NULL);CREATE UNIQUE INDEX user__idx ON    "USER" (        my_music_my_msc_id    ASC );ALTER TABLE "USER" ADD CONSTRAINT user_pk PRIMARY KEY ( id );ALTER TABLE my_music    ADD CONSTRAINT my_music_user_fk FOREIGN KEY ( user_id )        REFERENCES "USER" ( id );ALTER TABLE subscription_validity    ADD CONSTRAINT subs_val_track_list_fk FOREIGN KEY ( track_list_id )        REFERENCES track_list ( id );ALTER TABLE subscription_validity    ADD CONSTRAINT subs_val_user_fk FOREIGN KEY ( user_id )        REFERENCES "USER" ( id );ALTER TABLE track_list    ADD CONSTRAINT track_list_albums_fk FOREIGN KEY ( albums_id )        REFERENCES albums ( id );ALTER TABLE track_list    ADD CONSTRAINT track_list_my_music_fk FOREIGN KEY ( my_music_my_msc_id )        REFERENCES my_music ( my_msc_id );ALTER TABLE track_list    ADD CONSTRAINT track_list_performers_fk FOREIGN KEY ( performers_id )        REFERENCES performers ( id );ALTER TABLE "USER"    ADD CONSTRAINT user_my_music_fk FOREIGN KEY ( my_music_my_msc_id )        REFERENCES my_music ( my_msc_id );CREATE SEQUENCE my_msc_my_msc_id_seq START WITH 1 NOCACHE ORDER;CREATE OR REPLACE TRIGGER my_msc_my_msc_id_trg BEFORE    INSERT ON my_music    FOR EACH ROW    WHEN ( new.my_msc_id IS NULL )BEGIN    :new.my_msc_id := my_msc_my_msc_id_seq.nextval;END;/-- Oracle SQL Developer Data Modeler Summary Report: -- -- CREATE TABLE                             6-- CREATE INDEX                             2-- ALTER TABLE                             13-- CREATE VIEW                              0-- ALTER VIEW                               0-- CREATE PACKAGE                           0-- CREATE PACKAGE BODY                      0-- CREATE PROCEDURE                         0-- CREATE FUNCTION                          0-- CREATE TRIGGER                           1-- ALTER TRIGGER                            0-- CREATE COLLECTION TYPE                   0-- CREATE STRUCTURED TYPE                   0-- CREATE STRUCTURED TYPE BODY              0-- CREATE CLUSTER                           0-- CREATE CONTEXT                           0-- CREATE DATABASE                          0-- CREATE DIMENSION                         0-- CREATE DIRECTORY                         0-- CREATE DISK GROUP                        0-- CREATE ROLE                              0-- CREATE ROLLBACK SEGMENT                  0-- CREATE SEQUENCE                          1-- CREATE MATERIALIZED VIEW                 0-- CREATE MATERIALIZED VIEW LOG             0-- CREATE SYNONYM                           0-- CREATE TABLESPACE                        0-- CREATE USER                              0-- -- DROP TABLESPACE                          0-- DROP DATABASE                            0-- -- REDACTION POLICY                         0-- -- ORDS DROP SCHEMA                         0-- ORDS ENABLE SCHEMA                       0-- ORDS ENABLE OBJECT                       0-- -- ERRORS                                   0-- WARNINGS                                 0



Приложение 2 код программы


Класс DataBase
package sample.database;import javafx.event.ActionEvent;import sample.model.Performers;import sample.model.TrackList;import sample.model.User;import java.sql.*;import java.util.ArrayList;public class DataBase {    public static void main(String[] args) {        DataBase dataBase = new DataBase();        for (TrackList t:dataBase.allMusic()             ) {            System.out.println(t.toString());        }    }    //    public boolean loginIn(String email, String password) {        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            // запрос на проверку пароля и логина (эмэил в качестве логина)            ResultSet rset = stmt.executeQuery("SELECT * FROM users u INNER JOIN subscription_validity s ON u.id_users=s.id_users where email = '" + email + "'&& password ='" + password + "'");            if (rset.next()) {                User.setId(rset.getInt(1));                User.setName(rset.getString(3));                User.setEmail(rset.getString(4));                User.setPhone(rset.getString(5));                User.setCountry(rset.getString(6));                User.setStartDate(rset.getString(8));                User.setEndDate(rset.getString(9));                return true;            }        } catch (SQLException e) {            e.printStackTrace();        }        return false;    }    public boolean emailCheck(String email) {        try {        // подключение к db            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            // создание объекта Statement            Statement stmt = conn.createStatement();            // проверяем существует ли подобный эмэил в базе данных            ResultSet rset = stmt.executeQuery("select * from users where email = '" + email + "'");            return !rset.next();        } catch (SQLException e) {            e.printStackTrace();        }        return true;    }    public void registration(String name, String email, String phone, String password, String country, String startDate, String endDate) {        try {        // подключение к db            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            // создание объекта Statement            Statement stmt = conn.createStatement();            // запрос на всавку данных в таблицу users нового пользователя            stmt.executeUpdate("insert into users (email,password,name,phone_num,contry) value ('" + email + "','" + password + "','" + name + "','" + phone + "','" + country + "')");            // запрос на всавку данных в таблицу subscription_validity            stmt.executeUpdate("insert into subscription_validity (Start_Date_Start_Time, End_Date_End_Time,id_users) value ('" + startDate + "','" + endDate + "', (select max(id_users) from users) )");            stmt.close();            conn.close();        } catch (SQLException e) {            e.printStackTrace();        }    }    // запрос для музыки    public ArrayList<TrackList> allMusic() {        ArrayList<TrackList> arrayList = new ArrayList<>();        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            // запрос на вывод всех трэков с их автором и их альбомом            ResultSet rset = stmt.executeQuery("SELECT * FROM track_list t INNER JOIN performers p  ON p.id_performers=t.id_performers INNER JOIN albums a ON a.id_albums=t.id_albums");            while (rset.next()) {                arrayList.add(new TrackList(rset.getInt(1), rset.getString(2), rset.getString(10), rset.getString(8), rset.getString(3), rset.getString(4)));            }            rset.close();            stmt.close();            conn.close();        } catch (SQLException throwables) {            throwables.printStackTrace();        }        return arrayList;    }    // запрос для музыки    public ArrayList<TrackList> myMusic(int id) {        ArrayList<TrackList> arrayList = new ArrayList<>();        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            // запрос на вывод всех трэков с их автором и их альбомом            ResultSet rset = stmt.executeQuery("SELECT m.id_track_list, t.track_name, " +                    "a.album_name, p.performer_name, t.genre, t.duration FROM my_music m INNER JOIN " +                    "track_list t ON t.id_track_list=m.id_track_list INNER JOIN performers p ON " +                    "p.id_performers=t.id_performers INNER JOIN albums a ON a.id_albums=t.id_albums WHERE m.id_users=" + id);            while (rset.next()) {                arrayList.add(new TrackList(rset.getInt(1), rset.getString(2), rset.getString(3), rset.getString(4), rset.getString(5), rset.getString(6)));            }            rset.close();            stmt.close();            conn.close();        } catch (SQLException throwables) {            throwables.printStackTrace();        }        return arrayList;    }    public void addMyMusic(int idUser, int idTrackList) {        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            ResultSet rset = stmt.executeQuery("SELECT * FROM my_music WHERE " +                    "id_users = " + idUser + " && id_track_list = " + idTrackList);            // сюда передаем id user, которому будет добавлять музыку            // проверяем добавляли мы уже такое или нет            if (!rset.next())                stmt.executeUpdate("INSERT INTO my_music (id_users, id_track_list) VALUE ("+ idUser +", "+  idTrackList +")");            rset.close();            stmt.close();            conn.close();            // перед тем как добавить трэк проверяем есть ли тако        } catch (SQLException e){            e.printStackTrace();        }    }    public void deleteMyMusic(int idUser, int idTrackList) {        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            stmt.executeUpdate("DELETE FROM my_music WHERE id_users =" + idUser + "&& id_track_list =" + idTrackList);            stmt.close();            conn.close();        } catch (SQLException e){            e.printStackTrace();        }    }    // Запрос на сортировку по исполнителям    public ArrayList<Performers> performers(){        ArrayList<Performers> arrayList = new ArrayList();        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            ResultSet rset = stmt.executeQuery("SELECT * FROM performers");            while(rset.next()){                arrayList.add(new Performers(rset.getInt(1),rset.getString(2)));            }            rset.close();            stmt.close();            conn.close();        } catch (SQLException e){            e.printStackTrace();        }        return arrayList;    }    // запрос на вывод музыки с исполнителем    public ArrayList<TrackList> performerMusic(int id) {        ArrayList<TrackList> arrayList = new ArrayList<>();        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            // запрос на вывод всех трэков с их автором и их альбомом            ResultSet rset = stmt.executeQuery("SELECT * FROM track_list t INNER JOIN performers p  ON p.id_performers=t.id_performers INNER JOIN albums a ON a.id_albums=t.id_albums WHERE p.id_performers = " + id);            while (rset.next()) {                arrayList.add(new TrackList(rset.getInt(1), rset.getString(2), rset.getString(10), rset.getString(8), rset.getString(3), rset.getString(4)));            }            rset.close();            stmt.close();            conn.close();        } catch (SQLException throwables) {            throwables.printStackTrace();        }        return arrayList;    }    // запрос на удаление пользователя    public void deleteUser(int idUser) {        try {            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");            Statement stmt = conn.createStatement();            stmt.executeUpdate("DELETE FROM users WHERE id_users =" + idUser);            stmt.close();            conn.close();        } catch (SQLException e){            e.printStackTrace();        }    }}


Класс Main
package sample;import javafx.application.Application;import javafx.fxml.FXMLLoader;import javafx.scene.Parent;import javafx.scene.Scene;import javafx.stage.Modality;import javafx.stage.Stage;public class Main extends Application {    @Override    public void start(Stage primaryStage) throws Exception{        Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/login.fxml"));        Stage newStage = new Stage();        newStage.setScene(new Scene(root));        newStage.initModality(Modality.NONE);        newStage.show();    }    public static void main(String[] args) {        launch(args);    }}


Класс Music
package sample;import javafx.application.Application;import javafx.beans.value.ChangeListener;import javafx.beans.value.ObservableValue;import javafx.collections.FXCollections;import javafx.collections.ObservableList;import javafx.event.ActionEvent;import javafx.fxml.FXML;import javafx.fxml.FXMLLoader;import javafx.fxml.Initializable;import javafx.scene.Parent;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.ComboBox;import javafx.scene.control.ListView;import javafx.stage.Modality;import javafx.stage.Stage;import sample.database.DataBase;import sample.model.Performers;import sample.model.TrackList;import sample.model.User;import java.io.IOException;import java.net.URL;import java.util.ArrayList;import java.util.ResourceBundle;public class Music implements Initializable {    @FXML    public ListView<TrackList> allMusic;    @FXML    public ComboBox comboBox;    @FXML    public Button myMusic;    DataBase dataBase = new DataBase();    ArrayList<TrackList> arrayList;    TrackList trackList = null;    public void addMyMusic(ActionEvent actionEvent) {    // добавление музыки в "мой плейлист"        if(trackList != null)            dataBase.addMyMusic(User.getId(),trackList.getId());    }    @Override    public void initialize(URL location, ResourceBundle resources) {        arrayList = dataBase.allMusic();        ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);        allMusic.setItems(observableList);        allMusic.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TrackList>(){            @Override            public void changed(ObservableValue<? extends TrackList> observable, TrackList oldValue, TrackList newValue) {                trackList = newValue;            }        });        for (Performers p:dataBase.performers()        ) {            comboBox.getItems().add(p);        }        comboBox.getItems().add(new Performers(0,"All"));        comboBox.setValue(new Performers(0,"All"));        comboBox.valueProperty().addListener(new ChangeListener<Performers>() {            @Override            public void changed(ObservableValue<? extends Performers> observable, Performers oldValue, Performers newValue) {                if(newValue.getId() == 0) {                    arrayList = dataBase.allMusic();                    ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);                    allMusic.setItems(observableList);                } else{                    arrayList = dataBase.performerMusic(newValue.getId());                    ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);                    allMusic.setItems(observableList);                }            }        });    }    public void myMusic(ActionEvent actionEvent) {        Stage stage = (Stage) myMusic.getScene().getWindow();        stage.close();        try {            Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/mymusic.fxml"));            Stage newStage = new Stage();            newStage.setScene(new Scene(root));            newStage.initModality(Modality.NONE);            newStage.show();        } catch (IOException e) {            e.printStackTrace();        }    }}


Класс MyMusic
package sample;import javafx.beans.value.ChangeListener;import javafx.beans.value.ObservableValue;import javafx.collections.FXCollections;import javafx.collections.ObservableList;import javafx.event.ActionEvent;import javafx.fxml.FXML;import javafx.fxml.FXMLLoader;import javafx.fxml.Initializable;import javafx.scene.Parent;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.ListView;import javafx.stage.Modality;import javafx.stage.Stage;import sample.database.DataBase;import sample.model.TrackList;import sample.model.User;import javax.jws.soap.SOAPBinding;import java.io.IOException;import java.net.URL;import java.util.ArrayList;import java.util.ResourceBundle;public class MyMusic implements Initializable {    @FXML    public ListView allMusic;    @FXML    public Button back;    @FXML    public Button userData;    DataBase dataBase = new DataBase();    ArrayList<TrackList> arrayList;    TrackList trackList = null;    public void deleteMyMusic(ActionEvent actionEvent) {        if(trackList != null)            dataBase.deleteMyMusic(User.getId(),trackList.getId());        arrayList = dataBase.myMusic(User.getId());        ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);        allMusic.setItems(observableList);    }    @Override    public void initialize(URL location, ResourceBundle resources) {        arrayList = dataBase.myMusic(User.getId());        ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);        allMusic.setItems(observableList);        allMusic.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TrackList>(){            @Override            public void changed(ObservableValue<? extends TrackList> observable, TrackList oldValue, TrackList newValue) {                trackList = newValue;            }        });    }    public void userData(ActionEvent actionEvent) {        Stage stage = (Stage) userData.getScene().getWindow();        stage.close();        try {            Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/userdata.fxml"));            Stage newStage = new Stage();            newStage.setScene(new Scene(root));            newStage.initModality(Modality.NONE);            newStage.show();        } catch (IOException e) {            e.printStackTrace();        }    }    public void back(ActionEvent actionEvent) {        Stage stage = (Stage) back.getScene().getWindow();        stage.close();        try {            Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/music.fxml"));            Stage newStage = new Stage();            newStage.setScene(new Scene(root));            newStage.initModality(Modality.NONE);            newStage.show();        } catch (IOException e) {            e.printStackTrace();        }    }}


Класс Registration
package sample;import javafx.event.ActionEvent;import javafx.fxml.FXML;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.scene.control.PasswordField;import javafx.scene.control.TextField;import javafx.stage.Stage;import sample.database.DataBase;public class Registration {    @FXML    public Label error;    @FXML    public Button reg;    @FXML    public TextField phone;    @FXML    public PasswordField password;    @FXML    public PasswordField repeatPassword;    @FXML    public TextField name;    @FXML    public TextField email;    @FXML    public TextField endDate;    @FXML    public TextField startDate;    @FXML    public TextField country;    public void registration(ActionEvent actionEvent) {        DataBase dataBase = new DataBase();        // String name,String email,String phone, String password, String country, String startDate, String endDate        if(name.getText().matches("[a-zA-ZР-РР-С]{3,}")){                if(email.getText().matches("[A-Za-z0-9]{3,}[@][A-Za-z0-9]{3,}[.][A-Za-z0-9]{2,}")){                    if(phone.getText().matches("[+][7][0-9]{10}") || phone.getText().matches("[8][0-9]{10}")){                        if(dataBase.emailCheck(email.getText())){                            if(password.getText().equals(repeatPassword.getText())){                                dataBase.registration(name.getText(),email.getText(),                                        phone.getText(),password.getText(),country.getText(), startDate.getText(), endDate.getText());                                Stage stage = (Stage)reg.getScene().getWindow();                                stage.close();                            }else{                                error.setText("Пароли не совпадают");                            }                        }else{                            error.setText("Данный логин существует");                        }                    }else{                        error.setText("Введите телефон в формате +7********** или 8**********");                    }                }else{                    error.setText("Введите emeail в формате ***@***.**");                }        }else{            error.setText("Корректно введите имя");        }    }}


Класс Userdata
package sample;import javafx.event.ActionEvent;import javafx.fxml.FXML;import javafx.fxml.FXMLLoader;import javafx.fxml.Initializable;import javafx.scene.Parent;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.stage.Modality;import javafx.stage.Stage;import sample.database.DataBase;import sample.model.User;import java.io.IOException;import java.net.URL;import java.util.ResourceBundle;public class Userdata implements Initializable {    @FXML    public Label name;    @FXML    public Label email;    @FXML    public Label phone;    @FXML    public Label contry;    @FXML    public Label startDate;    @FXML    public Label endDate;    @FXML    public Button back;    @FXML    public Button delete;    @Override    public void initialize(URL location, ResourceBundle resources) {        name.setText(User.getName());        email.setText(User.getEmail());        phone.setText(User.getPhone());        contry.setText(User.getCountry());        startDate.setText(User.getStartDate());        endDate.setText(User.getEndDate());    }    public void back(ActionEvent actionEvent) {        Stage stage = (Stage) back.getScene().getWindow();        stage.close();        try {            Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/mymusic.fxml"));            Stage newStage = new Stage();            newStage.setScene(new Scene(root));            newStage.initModality(Modality.NONE);            newStage.show();        } catch (IOException e) {            e.printStackTrace();        }    }    public void delete(ActionEvent actionEvent) {        DataBase dataBase = new DataBase();        dataBase.deleteUser(User.getId());        Stage stage = (Stage) delete.getScene().getWindow();        stage.close();        try {            Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/login.fxml"));            Stage newStage = new Stage();            newStage.setScene(new Scene(root));            newStage.initModality(Modality.NONE);            newStage.show();        } catch (IOException e) {            e.printStackTrace();        }    }}


Класс Performers
package sample.model;public class Performers {    private int id;    private String name;    public Performers(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return name;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}


Класс TrackList
package sample.model;public class TrackList {    private int id;    private String trackName;    private String albumName;    private String performerName;    private String genre;    private String duration;    public TrackList(int id, String trackName, String albumName, String performerName, String genre, String duration) {        this.id = id;        this.trackName = trackName;        this.albumName = albumName;        this.performerName = performerName;        this.genre = genre;        this.duration = duration;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getTrackName() {        return trackName;    }    public void setTrackName(String trackName) {        this.trackName = trackName;    }    public String getAlbumName() {        return albumName;    }    public void setAlbumName(String albumName) {        this.albumName = albumName;    }    public String getPerformerName() {        return performerName;    }    public void setPerformerName(String performerName) {        this.performerName = performerName;    }    public String getGenre() {        return genre;    }    public void setGenre(String genre) {        this.genre = genre;    }    public String getDuration() {        return duration;    }    public void setDuration(String duration) {        this.duration = duration;    }    @Override    public String toString() {        return                trackName +                " by " + performerName  +                " from album - " + albumName +                " (" + genre + ") " +                ", " + duration;    }}


Класс User
package sample.model;public class User {    private static int id;    private static String name;    private static  String email;    private static String phone;    private static String country;    private static String startDate;    private static String endDate;    public static int getId() {        return id;    }    public static void setId(int id) {        User.id = id;    }    public static String getName() {        return name;    }    public static void setName(String name) {        User.name = name;    }    public static String getEmail() {        return email;    }    public static void setEmail(String email) {        User.email = email;    }    public static String getPhone() {        return phone;    }    public static void setPhone(String phone) {        User.phone = phone;    }    public static String getCountry() {        return country;    }    public static void setCountry(String country) {        User.country = country;    }    public static String getStartDate() {        return startDate;    }    public static void setStartDate(String startDate) {        User.startDate = startDate;    }    public static String getEndDate() {        return endDate;    }    public static void setEndDate(String endDate) {        User.endDate = endDate;    }}


Класс Login
package sample;import javafx.event.ActionEvent;import javafx.fxml.FXML;import javafx.fxml.FXMLLoader;import javafx.scene.Parent;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.scene.control.PasswordField;import javafx.scene.control.TextField;import javafx.stage.Modality;import javafx.stage.Stage;import sample.database.DataBase;import java.io.IOException;public class Login {    @FXML    public Button loginIn;    @FXML    public Button registration;    @FXML    public TextField email;    @FXML    public PasswordField password;    @FXML    public Label error;    public void loginIn(ActionEvent actionEvent) {        DataBase dataBase = new DataBase();        if(email.getText() != null ){            if(password.getText() != null){                if(dataBase.loginIn(email.getText(), password.getText())){                    Stage stage = (Stage) loginIn.getScene().getWindow();                    stage.close();                    try {                        Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/music.fxml"));                        Stage newStage = new Stage();                        newStage.setScene(new Scene(root));                        newStage.initModality(Modality.NONE);                        newStage.show();                    } catch (IOException e) {                        e.printStackTrace();                    }                } else {                    error.setText("This Login or Password does not exist");                }            } else {                error.setText("Please, enter your Password: ");            }        } else {            error.setText("Please, enter your Login: ");        }    }    public void registration(ActionEvent actionEvent) {        try {            Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/registration.fxml"));            Stage newStage = new Stage();            newStage.setScene(new Scene(root));            newStage.initModality(Modality.NONE);            newStage.show();        } catch (IOException e) {            e.printStackTrace();        }    }}

Подробнее..
Категории: Sql , Java , Oracle , Sql server , Intellij idea , Phpmyadmin

Перевод Flame-графики огонь из всех движков

12.10.2020 18:16:09 | Автор: admin

Всем снова привет! Приглашаем на онлайн-встречу с Василием Кудрявцевым (директором департамента обеспечения качества в АО РТЛабс), которая пройдёт в рамках курса Нагрузочное тестирование. И публикуем перевод статьи Michael Hunger software developer and editor ofNeo4j Developer BlogandGRANDstack!

Стандартные профайлеры Java используют либо инструментарий байткода, либо сэмплинг (рассматривая трассировки стека через короткие промежутки времени), чтобы определить, где было потрачено время. В обоих подходах есть свои недостатки и странности. Понимание результатов работы таких профайлеров само по себе искусство и требует большого опыта.

К счастью, Брендан Грегг, инженер по производительности в Netflix, придумал flame-графики, гениального вида диаграммы для трассировки стека, которые можно собрать практически из любой системы.

Flame-график сортирует и агрегирует трассировки на каждом уровне стека, таким образом их количество отражает процент от общего времени, затраченного в этой части кода. Визуализация этих блоков в виде прямоугольников с шириной, пропорциональной проценту от общего времени работы и укладка их друг на друга оказалась крайне полезной визуализацией. (Обратите внимание на то, что порядок слева направо не имеет принципиального значения, часто это просто сортировка по алфавиту. То же самое и с цветами. Значение имеют только относительная ширина и глубина стека)

Flame-график бенчмарка заполнения непредаллоцированного ArrayListFlame-график бенчмарка заполнения непредаллоцированного ArrayList

Flame'ы снизу вверх отражают прогрессию от точки входа программы или потока (main или цикл событий) до листьев выполнения на концах flame'ов.

Вы сразу увидите, если некоторые части программы будут занимать неожиданно большое количество времени. Чем выше на диаграмме вы это видите, тем хуже. А если у вас есть очень широкий flame сверху, то вы точно нашли узкое место, где работа не делегируется куда-либо еще. После устранения проблемы, измерьте время работы еще раз, если проблема с производительностью сохранится, вернитесь к диаграмме, чтобы понять, где осталась проблема.

Для устранения недостатков стандартных профайлеров многие современные инструменты используют внутреннюю функцию JVM (AsyncGetCallTrace), которая позволяет собирать трассировки стека независимо от безопасных точек. Помимо этого, они объединяют измерение JVM-операций с нативным кодом и системных вызовов операционной системы, так что время, проведенное в сети, ввод/вывод или сборка мусора, также может стать частью flame-графа.

Такие инструменты, как Honest Profiler, perf-map-agent, async-profiler или даже IntelliJ IDEA, умеют захватывать информацию и с легкостью делать flame-графики.

В большинстве случаев вы просто скачиваете инструмент, предоставляете PID вашего Java-процесса и говорите инструменту работать в течение определенного периода времени и генерировать интерактивный SVG.

# download and unzip async profiler for your OS from:# https://github.com/jvm-profiling-tools/async-profiler./profiler.sh -d <duration> -f flamegraph.svg -s -o svg <pid> && \open flamegraph.svg  -a "Google Chrome"

SVG, создаваемый этими инструментами, не просто красочный, но и интерактивный. Вы можете увеличивать секции, искать посимвольно и т.д.

Flame-графики впечатляющий и мощный инструмент для быстрого обзора характеристик производительности ваших программ. С ними вы можете сразу увидеть проблемные места и сосредоточиться на них, а обзор аспектов, не связанных с JVM, помогает получить более полную картину.


Интересно развиваться в данном направлении? Запишитесь на бесплатный Demo-урок Скрипты и сценарии нагрузочного тестирования- Performance center (PC) и Vugen!

Подробнее..

Скринкаст простейший плагин для IntelliJ IDEA

02.09.2020 00:22:36 | Автор: admin
Многие боятся писать плагины для IDE потому, что кто-то их напугал, что это безумно сложно. Это не совсем так: простые плагины для бытовых нужд писать вполне возможно.

Давайте сделаем простейший плагин, который при нажатии на Ctrl+T весь текст, выделенный в редакторе, отправляет прямиком в Twitter. Потом каждый его сможет заточить для собственных нужд.

В идеале, такая штука пишется за пару минут, при этом демонстрируя идею, как начать создавать свои плагины.

Подробнее..

Перевод Удаленная отладка Spring Boot приложений (IntelliJ Eclipse)

01.10.2020 00:23:40 | Автор: admin

Локальная разработка на вашей машине удобна. Но как только вы развернете свое приложение, у вас будет совсем другая среда, что может привести к непредвиденному поведению или ошибкам. Использование ручной печати


System.out.println (Теперь мы находимся здесь, а переменная X is = + x); 

делает код вашего приложения довольно громоздким и его выполнение занимает много времени. К счастью в Java есть зрелая отладочная экосистема. Это позволяет нам удаленно отлаживать приложения Spring Boot и анализировать его рабочий процесс на удаленном сервере / облаке.


Чтобы показать вам, насколько на самом деле проста удаленная отладка с помощью Java, я буду использовать приложение Spring Boot 2.3, работающее на Java 11. Я разверну приложение как в виде контейнера Docker, так и с помощью старой школы java -jar way. Наконец, вы узнаете, как удаленно отлаживать интерфейс REST с помощью IntelliJ IDEA (2019.1) и Eclipse (2019-03).


Настройка проекта


Для приложения я выбрал простое приложение Spring Boot 2.3. Он включает в себя встроенную базу данных H2 и JPA для обслуживания книг, которые генерируются случайным образом при запуске.


Класс контроллера, который я позже буду использовать для отладки, выглядит следующим образом:


@Slf4j@RestController@RequestMapping("/api/books")public class BookController {    @Autowired    private BookRepository bookRepository;    @GetMapping    public List<Book> getAllBooks() {        log.info("Retrieving all available books");        List<Book> allAvailableBooks = bookRepository.findAll();        return allAvailableBooks;    }    @GetMapping("/{id}")    public Book getBookById(@PathVariable("id") Long id) {        log.info("Retrieving book with id: {}", id);        Optional<Book> book = bookRepository.findById(id);        if (book.isEmpty()) {            throw new BookNotFoundException("Can't find book with id: " + id);        }        return book.get();    }}

Остальная часть приложения опущена, но вы можете найти весь исходный код на GitHub.


Развертывание


Чтобы иметь возможность удаленной отладки вашего Java-приложения Spring Boot, мы должны передать в JVM следующие аргументы:


-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000

Указанный порт в этом аргументе позже используется для подключения наших IDE к работающей JVM и не должен совпадать с портом приложения.


Аргумент может быть передан в JVM с помощью следующей команды:


java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 -jar target/remote-debugging-spring-boot-application.jar

При использовании Docker мы можем добавить этот аргумент в нашу ENTRYPOINT и просто нужно отобразить дополнительный порт при запуске Docker контейнера:


FROM openjdk:11-jdk-slimVOLUME /tmpCOPY target/remote-debugging-spring-boot-application.jar app.jarENTRYPOINT ["java","-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000","-jar","/app.jar"]

docker run -d -p 8080:8080 -p 8000:8000 --name debug-me debug-me

Важное замечание: Чтобы выполнить удаленную отладку на удаленном сервере в следующих шагах, убедитесь, что никакое правило брандмауэра не блокирует этот порт, и вы можете получить к нему доступ с локального компьютера.


Удаленная отладка приложений Spring Boot с помощью IntelliJ IDEA
Удаленная отладка приложения Spring Boot с IntelliJ IDEA требует, чтобы вы открыли проект (исходный код) с IntelliJ. Затем мы можем перейти к редактированию конфигурации в правом верхнем углу рядом с зеленой кнопкой запуска:



Выберите создание новой конфигурации Run / Debug, нажимая кнопку +, и выберите Remote. Старые версии IntelliJ могут иметь разные названия, такие как Remote Debugging (удаленная отладка), Debugging (отладка) и т.д.:



Затем введите имя для выбранной вами конфигурации удаленной отладки и укажите порт и хост, к которому вы хотите подключиться (в нашем примере это порт 8000). Убедитесь, что вы выбрали правильный проект для Use module classpath (Использовать classpath модуля) и нажмите Apply или Ok:



Теперь отметьте в исходном коде строку, которую вы хотите отладить, и запустите конфигурацию удаленной отладки Remote debugging configuration(убедитесь, что ваше приложение уже запущено):



Теперь попробуйте получить доступ к конечной точке (http://localhost:8080/api/books в этом примере) с помощью браузера или клиента API, и вы сможете отладить указанную часть вашего приложения в вашей IDEA:



Чтобы остановить удаленную отладку приложения, просто нажмите красную кнопку остановки.


Удаленная отладка приложений Spring Boot в Eclipse


Для Eclipse вы также сначала должны импортировать проект, а затем перейти к пункту меню Run -> Debug Configurations:

Подробнее..
Категории: Java , Eclipse , Spring boot , Debug , Intellij idea

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru