SlideShare a Scribd company logo
How Idea plugin can help
you in QA Automation
Delex Conf, Minsk, 2019
Артем
Ерошенко
QametaSoftware
Иван
Варивода
Wrike
Приложение для управления проектами
Более 17 000 компаний из 120 стран
Google,AirBnb,Adobe,Western Union, …
Автоматизация тестирования
Много разных проектов
В каждом проекте сюрприз
Велосипед
СложныйСложный
СложныйМонстр
Расставить все по местам
Расставить все по местамКак обеспечить равномерность?
Ограничить прогресс
Быстро догонять
Добавить пару рук
Совершенствовать орудие труда
Я видел много инструментов
Возможности Idea Plugin
Тесты катать - не ракеты строить
Тесты катать - не ракеты строить
Атомарные
Похожие
Простые
Доктор, я перевожу
тесты на jUnit5 силой мысли,
это нормально?
Подъем по ступенькам
Вперед к приключениям
Импорт в проект
Экспорт из проекта
Рефакторинг проекта
Плагин за 10 минут
Импорт в проект
Экспорт из проекта
Рефакторинг проекта
Плагин за 10 минут
Создаем новый проект
New > Plugin DevKit > Action
Форма создания Action
Заполняем Action ID
Заполняем Class Name
Заполняем Name
Добавляем Action в группу
Группа меню правого клика
Добавили Action в группу
Точка входа в Idea Plugin
public class AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
//MAGIC here
}
}
public class FirstAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getProject();
Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
PsiElement psiElement = e.getData(PSI_ELEMENT);
String name = ((PsiClass)psiElement).getQualifiedName();
showDialog(cName);
}
}
Основа всего PsiElement
public class Steps {
@Step("Open Wrike login page")
public void openWrikeLogin() { ... }
@Step("Open inbox")
public void openInbox() { ... }
...
}
PsiClass - работа с классом
PsiMethod - работа с методами
public class Steps {
@Step("Open Wrike login page")
public void openWrikeLogin() { ... }
@Step("Open inbox")
public void openInbox() { ... }
...
}
PsiAnnotation - аннотации
public class Steps {
@Step("Open Wrike login page")
public void openWrikeLogin() { ... }
@Step("Open inbox")
public void openInbox() { ... }
...
}
public class FirstAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
PsiElement psiElement = e.getData(PSI_ELEMENT);
PsiClass psiClass = ((PsiClass)psiElement);
String name = psiClass.getQualifiedName();
showDialog(name);
}
}
Выведем Qualified Name
Создаем обычную панель
Рисуем диалоговое окно
Выполняем ./gradlew runIde
Импорт в проект
Экспорт из проекта
Рефакторинг проекта
Плагин за 10 минут
Какую проблему решаем?
10 K селениум тестов
Рассмотрим обычный тест
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Категории автотеста
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
На самом деле их много
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class,
Workspace.class, IE.class, Safari.class
...})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Технические категории
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Продуктовые категории
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Нужно поменять разметку
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Добавить продуктовые фичи
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@Feature("Login")
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Добавить технические типы
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@Feature("Login")
@Types({@Type("Firefox"), @Type("Smoke")})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Удалить ненужные категории
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Feature("Login")
@Types({@Type("Firefox"), @Type("Smoke")})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Какую проблему решаем?Способ решения проблемы
Давайте запилим плагин!
ReplaceCategoryAction
public class ReplaceCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT)
if (element instance of PsiClass) {
replaceCategory((PsiClass)element)
}
}
}
Получили PsiClass
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Берем все методы класса
private void replaceCategory(PsiClass psiClass) {
Arrays.stream(psiClass.getMethods())
.filter(m -> m.hasAnnotation("...Test"))
.filter(m -> m.hasAnnotation("...Category"))
.forEach(this::replaceCategory);
}
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Получили все методы класса
Фильтруем нужные методы
private void replaceCategory(PsiClass psiClass) {
Arrays.stream(psiClass.getMethods())
.filter(m -> m.hasAnnotation("...Test"))
.filter(m -> m.hasAnnotation("...Category"))
.forEach(this::replaceCategory);
}
Получили тестовый метод
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Берем текст @Category
private void replaceCategory(PsiMethod method) {
PsiAnnotation category =
method.getAnnotation("org...Category");
String categoryValues = category.
findDeclaredAttributeValue(“value”).getText();
//next slide plz
}
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Получили текст @Category
Технические и продуктовые
private void replaceCategory(PsiMethod method) {
String categoryValues = ...
String featureValue =
findFeatureValue(categoryValues)
List<String> typeValues =
findTypeValues(categoryValues)
//next slide plz
}
public class LoginTest {
@Before()
public void prepare() { ... }
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Технические и продуктовые
Добавляем @Feature
private void replaceCategory(PsiMethod method) {
String featureValue = ...
List<String> typeValues = ...
addFeature(method, featureValue);
addTypes(method, typeValues);
removeCategory(method);
}
Добавляем аннотацию
void addFeature(PsiMethod method, String feature) {
String body = "io...Feature("" + feature + "")";
PsiAnnotation featureAnnotation =
method.getModifierList().addAnnotation(body);
JavaCodeStyleManager.getInstance(project)
.shortenClassReferences(featureAnnotation);
}
@io.qameta.Feature("Login")
Добавили с полным путем
public class LoginTest {
@Before()
public void prepare() { ... }
@io.qameta.allure.Feature("Login")
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Оптимизируем импорты
void addFeature(PsiMethod method, String feature) {
String body = "io...Feature("" + feature + "")";
PsiAnnotation featureAnnotation =
method.getModifierList().addAnnotation(body);
JavaCodeStyleManager.getInstance(project)
.shortenClassReferences(featureAnnotation);
}
@Feature("Login")
Оптимизировали импорты
public class LoginTest {
@Before()
public void prepare() { ... }
@Feature("Login")
@Test
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Нельзя просто взять
и изменить код!
private void replaceCategory(PsiMethod psiMethod) {
CommandProcessor.getInstance().executeCommand(()->{
getApplication().runWriteAction(()->{
//код с предыдущего слайда
})
})
}
Обертка над изменением кода
Добавляем @Types
private void replaceCategory(PsiMethod method) {
String featureValue = ...
List<String> typeValues = ...
addFeature(method, featureValue);
addTypes(method, typeValues);
removeCategory(method);
}
Все по накатанной схеме
void addTypes(PsiMethod method, List<String> types) {
String body = createTypesBody(types);
PsiAnnotation typesAnnotation =
method.getModifierList().addAnnotation(body);
JavaCodeStyleManager.getInstance(project)
.shortenClassReferences(featureAnnotation);
}
@Types({@Type("Firefox"), @Type("Smoke")})
Аннотация в виде строки
public String createTypesBody(List<String> types) {
return types.stream()
.map(f -> "@io..Type("" + f + "")")
.collect(Collectors.joining(","))
}
public String getTypesText(List<String> types) {
String inner = getInnerFeaturesText(features);
return "@io..Types({" + inner + "})";
}
@Types({@Type("Firefox"), @Type("Smoke")})
Добавили @Types
public class LoginTest {
@Before()
public void prepare() { ... }
@Feature("Login")
@Test
@Types({@Type("Firefox"), @Type("Smoke")})
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
}
Удаляем @Category
private void replaceCategory(PsiMethod method) {
String featureValue = ...
List<String> typeValues = ...
addFeature(method, featureValue);
addTypes(method, typeValues);
removeCategory(method);
}
public class LoginTest {
@Before()
public void prepare() { ... }
@Feature("Login")
@Test
@Types({@Type("Firefox"), @Type("Smoke")})
@Category({Firefox.class, Smoke.class, Login.class})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
Удаляем @Category
private void deleteCategory(PsiMethod method) {
PsiAnnotation category = method
.getModifierList()
.findAnnotation("org...Category");
category.delete();
}
Удаляем @Category
Удалили @Category
public class LoginTest {
@Before()
public void prepare() { ... }
@Feature("Login")
@Test
@Types({@Type("Firefox"), @Type("Smoke")})
@DisplayName("Check login")
public void loginWithCorrectPassword() { ... }
А для всех тестов?
ReplaceAllCategoryAction
public class ReplaceAllCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
project = e.getProject();
AllClassesGetter.processJavaClasses(
new PlainPrefixMatcher(""),
project,
GlobalSearchScope.allScope(project),
processor);
}
}
Начинается с "Test"
public class ReplaceAllCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
project = e.getProject();
AllClassesGetter.processJavaClasses(
new PlainPrefixMatcher("Test"),
project,
GlobalSearchScope.allScope(project),
processor);
}
}
Обходим все классы
public class ReplaceAllCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
project = e.getProject();
AllClassesGetter.processJavaClasses(
new PlainPrefixMatcher(""),
project,
GlobalSearchScope.allScope(project),
processor);
}
}
Указываем проект
public class ReplaceAllCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
project = e.getProject();
AllClassesGetter.processJavaClasses(
new PlainPrefixMatcher(""),
project,
GlobalSearchScope.allScope(project),
processor);
}
}
Скоуп поиска
public class ReplaceAllCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
project = e.getProject();
AllClassesGetter.processJavaClasses(
new PlainPrefixMatcher(""),
project,
GlobalSearchScope.allScope(project),
processor);
}
}
Логика обработки
public class ReplaceAllCategoryAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
project = e.getProject();
AllClassesGetter.processJavaClasses(
new PlainPrefixMatcher(""),
project,
GlobalSearchScope.allScope(project),
processor);
}
}
Метод используемый выше
Processor<PsiClass> processor = psiClass -> {
replaceCategory(psiClass);
return true;
};
Какие еще есть идеи?
Разметка новых тестов
Переезд на новую версию
Импорт в проект
Экспорт из проекта
Рефакторинг проекта
Плагин за 10 минут
Какую проблему решаем?
Что автоматизировано?
Задача на автоматизацию
public class AddFavoritesAfterNoteTest {
@Test
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
}
Возьмем название теста
Поиск по тексту в Jira
Нужно расставить ссылки
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@TmsLink("AE-4")
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
}
Для всех тестов сложно
Какую проблему решаем?Способ решения проблемы
Idea Plugin
Работаем только с классами
public class JiraKeyImportAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT)
if (element instance of PsiClass) {
addTmsLinkToClassMethods((PsiClass)element)
}
}
}
Выбрали класс для апдейта
public class AddFavoritesAfterNoteTest {
@Test
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
private void utilityMethod() { ... }
}
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToClassMethods(PsiClass testClass) {
Arrays.stream(testClass.getMethods())
.filter(m -> m.hasAnnotation("org..Test"))
.filter(m -> m.hasAnnotation("org..DisplayName"))
.forEach(this::addTmsLinkToMethod)
}
}
Получаем все методы класса
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToClassMethods(PsiClass testClass) {
Arrays.stream(testClass.getMethods())
.filter(m -> m.hasAnnotation("org..Test"))
.filter(m -> m.hasAnnotation("org..DisplayName"))
.forEach(this::addTmsLinkToMethod)
}
}
Фильтруем тестовые методы
Выбрали методы для апдейта
public class AddFavoritesAfterNoteTest {
@Test
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
private void utilityMethod() { ... }
}
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod method) {
PsiAnnotatation nameAnnotation =
method.getAnnotation("org..DisplayName");
String text = name
.findDeclaredAttributeValue("value").getText()
String key = jiraClient.findByText(text);
addTmsLinkToMethod(testMethod, key);
}
}
Берем аннотацию DisplayName
Нашли аннотацию с именем
public class AddFavoritesAfterNoteTest {
@Test
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
private void utilityMethod() { ... }
}
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod testMethod) {
PsiAnnotatation name =
method.getAnnotation("org..DisplayName");
String text = name
.findDeclaredAttributeValue("value").getText()
String key = jiraClient.findByText(text);
addTmsLinkToMethod(testMethod, key);
}
}
Берем текст DisplayName
Тест для поиска в Jira
public class AddFavoritesAfterNoteTest {
@Test
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
private void utilityMethod() { ... }
}
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod testMethod) {
PsiAnnotatation name =
method.getAnnotation("org..DisplayName");
String text = name
.findDeclaredAttributeValue("value").getText()
String key = jiraClient.findByText(text);
addTmsLinkToMethod(testMethod, key);
}
}
Ищем Issue по тексту в Jira
Посмотрим на Jira API
Поиск по тексту в Jira
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod testMethod) {
PsiAnnotatation name =
method.getAnnotation("org..DisplayName");
String text = name
.findDeclaredAttributeValue("value").getText()
String key = jiraClient.findByText(text);
addTmsLinkToMethod(testMethod, key);
}
}
Добавляем аннотацию в код
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod method,String key){
String tmsLinksAnnotationText =
"@io..TmsLink("" + key + "")";
PsiAnnotation tmsLink =
method.addAnnotation(tmsLinkAnnotationText);
JavaCodeStyleManager.getInstance(project)
.shortenClassReferences(tmsLink);
}
}
Формируем текст аннотации
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod method,String key){
String tmsLinksAnnotationText =
"@io..TmsLink("" + key + "")";
PsiAnnotation tmsLink =
method.addAnnotation(tmsLinkAnnotationText);
JavaCodeStyleManager.getInstance(project)
.shortenClassReferences(tmsLink);
}
}
Добавляем аннотацию
Добавили аннотацию
public class AddFavoritesAfterNoteTest {
@Test
@io.qameta.allure.TmsLink("AE-5")
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
}
public class JiraKeyImportAction extends AnAction {
void addTmsLinkToMethod(PsiMethod method,String key){
String tmsLinksAnnotationText =
"@io..TmsLink("" + key + "")";
PsiAnnotation tmsLink =
method.addAnnotation(tmsLinkAnnotationText);
JavaCodeStyleManager.getInstance(project)
.shortenClassReferences(tmsLink);
}
}
Формируем текст аннотации
Добавили импорт аннотации
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
}
Что еще можно прокачать?
Теги для
запуска тестов
Labels для разметки тестов
Создаем JiraLabelsImportAction
public class JiraLabelsImportAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT)
if (element instance of PsiClass) {
addTagsToClassMethods((PsiClass)element)
}
}
}
public class JiraLabelsImportAction extends AnAction {
void addTagsToClassMethods(PsiClass testClass) {
Arrays.stream(testClass.getMethods())
.filter(m -> m.hasAnnotation("org..TmsLink"))
.forEach(this::addTmsLinkToMethod)
}
}
Фильтруем методы с ключом
public class JiraLabelsImportAction extends AnAction {
void addTagsToMethod(PsiMethod testMethod) {
PsiAnnotation tmsLink =
testMethod.getAnnotation("io..TmsLink");
String key = getTextValue(tmsLink);
List<String> labels = jiraClient.getLables(key);
String tagsText = getTagsText(labels);
PsiAnnotation tags = create(tagsText, testMethod);
testMethod.getModifiersList()
.addAfter(tags, tmsLink);
}
}
Зачитываем ключ Issue в Jira
Получили ключи тестов
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
@Test
@TmsLink("AE-4")
@DisplayName("Удаление из избранного после удаления заметки")
public void shouldDeleteToFavoriteAfterNodeTest() { ... }
}
public class JiraLabelsImportAction extends AnAction {
void addTagsToMethod(PsiMethod testMethod) {
PsiAnnotation tmsLink =
testMethod.getAnnotation("io..TmsLink");
String key = getTextValue(tmsLink);
List<String> labels = jiraClient.getLables(key);
String tagsText = getTagsText(labels);
PsiAnnotation tags = create(tagsText, testMethod);
testMethod.getModifiersList()
.addAfter(tags, tmsLink);
}
}
Забираем лейбочки из Jira
Labels для разметки тестов
public class JiraLabelsImportAction extends AnAction {
void addTagsToMethod(PsiMethod testMethod) {
PsiAnnotation tmsLink =
testMethod.getAnnotation("io..TmsLink");
String key = getTextValue(tmsLink);
List<String> labels = jiraClient.getLables(key);
String tagsText = getTagsText(labels);
PsiAnnotation tags = create(tagsText, testMethod);
testMethod.getModifiersList()
.addAfter(tags, tmsLink);
}
}
Создаем строку с аннотацией
Аннотация в виде строки
public String getInnerTagsText(List<String> labels) {
return features.stream()
.map(f -> "@org..Tag("" + f + "")")
.collect(Collectors.joining(","))
}
public String getTagsText(List<String> labels) {
String inner = getInnerTagsText(labels);
return "@org..Tag({" + inner + "})";
}
@org..Tags({@org..Tag("regress"), ...})
public class JiraLabelsImportAction extends AnAction {
void addTagsToMethod(PsiMethod testMethod) {
PsiAnnotation tmsLink =
testMethod.getAnnotation("io..TmsLink");
String key = getTextValue(tmsLink);
List<String> labels = jiraClient.getLables(key);
String tagsText = getTagsText(labels);
PsiAnnotation tags = create(tagsText, testMethod);
testMethod.getModifiersList()
.addAfter(tags, tmsLink);
}
}
Создаем аннотацию из строки
public class JiraLabelsImportAction extends AnAction {
void addTagsToMethod(PsiMethod testMethod) {
PsiAnnotation tmsLink =
testMethod.getAnnotation("io..TmsLink");
String key = getTextValue(tmsLink);
List<String> labels = jiraClient.getLables(key);
String tagsText = getTagsText(labels);
PsiAnnotation tags = create(tagsText, testMethod);
testMethod.getModifiersList()
.addAfter(tags, tmsLink);
}
}
@Tags после @TmsLink
Добавили теги в тест
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@org..Tags({@org..Tag("regress")})
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
}
public class JiraLabelsImportAction extends AnAction {
void addTagsToMethod(PsiMethod testMethod) {
addImport(testMethod.getContainingFile(), "org..Tag");
addImport(testMethod.getContainingFile(), "org..Tags");
// код из предыдущего слайда с write операцией
optimizeImports(testMethod.getContainingFile());
}
}
Оптимизируем импорты
Добавили теги в тест
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@Tags({@Tag("regress")})
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() { ... }
}
Оптимизировали импорты
Какие еще есть идеи?
Импорт сценария теста
Импорт Feature/Story
Включение/выключение
Импорт в проект
Экспорт из проекта
Рефакторинг проекта
Плагин за 10 минут
Какую проблему решаем?
А что в тестах проверяется?
Задача на автоматизацию
Какую проблему решаем?Способ решения проблемы
Как будем действовать?
Как будем действовать?
Код метода
автотеста
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@Features({@Feature("Favorites")})
@Stories({@Story("Add favorites after note")})
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() {
steps.openMainPage();
...
}
}
Мета информация
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@Features({@Feature("Favorites")})
@Stories({@Story("Add favorites after note")})
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() {
steps.openMainPage();
...
}
}
Сценарий теста
Как будем действовать?
Код метода
автотеста
Соберем
все
данные
Информация для экспорта
public class TestCase implements Serializable {
private String id;
private String name;
private List<String> features;
private List<String> stories;
private List<String> steps;
...
}
Как будем действовать?
Код метода
автотеста
Соберем
все
данные
Сохраним
в удобном
формате
HTML-шаблон на Freemarker
Давайте запилим плагин!
Создаем TestCaseExportAction
public class TestCaseExportAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT);
List<TestCase> testcases =
Arrays.stream(((PsiClass) element).getMethods())
.filter(m -> m.hasAnnotation("org..Test"))
.map(this::getTestCaseFromMethod)
.collect(Collectors.toList());
writeTestCases(event.getProject(), testcases);
}
}
Берем все методы тестов
public class TestCaseExportAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT);
List<TestCase> testcases =
Arrays.stream(((PsiClass) element).getMethods())
.filter(m -> m.hasAnnotation("org..Test"))
.map(this::getTestCaseFromMethod)
.collect(Collectors.toList());
writeTestCases(event.getProject(), testcases);
}
}
Собираем информацию
public class TestCaseExportAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT);
List<TestCase> testcases =
Arrays.stream(((PsiClass) element).getMethods())
.filter(m -> m.hasAnnotation("org..Test"))
.map(this::getTestCaseFromMethod)
.collect(Collectors.toList());
writeTestCases(event.getProject(), testcases);
}
}
Сохраняем результат
public class TestCaseExportAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
PsiElement element = event.getData(PSI_ELEMENT);
List<TestCase> testcases =
Arrays.stream(((PsiClass) element).getMethods())
.filter(m -> m.hasAnnotation("org..Test"))
.map(this::getTestCaseFromMethod)
.collect(Collectors.toList());
writeTestCases(event.getProject(), testcases);
}
}
Как собираем данные метода?
public class TestCaseExportAction extends AnAction {
public TestCase getTestCaseFromMethod(PsiMethod method) {
TestCase testCase = new TestCase();
testCase.setId(getTmsLinkText(method));
testCase.setName(getDisplayNameText(method));
testCase.setFeature(getFeaturesText(method));
testCase.setStories(getStoriesText(method));
testCase.setSteps(getSteps(method));
return testCase;
}
}
Уже делали раньше
public class TestCaseExportAction extends AnAction {
public TestCase getTestCaseFromMethod(PsiMethod method) {
TestCase testCase = new TestCase();
testCase.setId(getTmsLinkText(method));
testCase.setName(getDisplayNameText(method));
testCase.setFeature(getFeaturesText(method));
testCase.setStories(getStoriesText(method));
testCase.setSteps(getSteps(method));
return testCase;
}
}
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@Features({@Feature("Favorites")})
@Stories({@Story("Add favorites after note")})
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() {
steps.openMainPage();
...
}
}
Уже делали раньше
Тоже самое, только массивы
public class TestCaseExportAction extends AnAction {
public TestCase getTestCaseFromMethod(PsiMethod method) {
TestCase testCase = new TestCase();
testCase.setId(getTmsLinkText(method));
testCase.setName(getDisplayNameText(method));
testCase.setFeature(getFeaturesText(method));
testCase.setStories(getStoriesText(method));
testCase.setSteps(getSteps(method));
return testCase;
}
}
public class AddFavoritesAfterNoteTest {
@Test
@TmsLink("AE-5")
@Features({@Feature("Favorites")})
@Stories({@Story("Add favorites after note")})
@DisplayName("Добавление в избранное после создания заметки")
public void shouldAddToFavoriteAfterNodeTest() {
steps.openMainPage();
...
}
}
Тоже самое, только массивы
А вот это не тривиально
public class TestCaseExportAction extends AnAction {
public TestCase getTestCaseFromMethod(PsiMethod method) {
TestCase testCase = new TestCase();
testCase.setId(getTmsLinkText(method));
testCase.setName(getDisplayNameText(method));
testCase.setFeature(getFeaturesText(method));
testCase.setStories(getStoriesText(method));
testCase.setSteps(getSteps(method));
return testCase;
}
}
public class AddFavoritesAfterNoteTest {
@Test
public void shouldAddToFavoriteAfterNodeTest() {
steps.openMainPage();
steps.openAutoCardPage("Volvo XC90")
steps.addNotesToAutoCard("в хорошем состоянии");
steps.openFavoritesPage("в хорошем состоянии");
steps.checkFavoritesListContains("Volvo XC90")
}
}
Сценарий теста
Добываем список Шагов
public class ScenarioExportAction extends AnAction {
public List<String> getSteps(PsiMethod testMethod) {
Arrays.stream(method.getBody().getStatements())
.filter(PsiMethodCallExpression.class::isInstance)
.map(PsiMethodClassExpression.class::cast)
.map(PsiMethodClassExpression::resolveMethod())
.filter(m -> m.hasAnnotation("...Step"))
.map(m -> m.getAnnotation("...Step"))
.map(a -> a.findDeclaredAttribute("value"))
.map(PsiAnnotationMemberValue::getText)
.forEach(Collectors.toList())
}
}
public class AddFavoritesAfterNoteTest {
@Test
public void shouldAddToFavoriteAfterNodeTest() {
steps.openMainPage();
steps.openAutoCardPage("Volvo XC90")
steps.addNotesToAutoCard("в хорошем состоянии");
steps.openFavoritesPage();
steps.checkFavoritesListContains("Volvo XC90")
}
}
Не метод, а оператор вызова
PsiMethodCallExpression
Добываем список методов
public class ScenarioExportAction extends AnAction {
public List<String> getSteps(PsiMethod testMethod) {
Arrays.stream(method.getBody().getStatements())
.filter(PsiMethodCallExpression.class::isInstance)
.map(PsiMethodClassExpression.class::cast)
.map(PsiMethodClassExpression::resolveMethod)
.filter(m -> m.hasAnnotation("...Step"))
.map(m -> m.getAnnotation("...Step"))
.map(a -> a.findDeclaredAttribute("value"))
.map(PsiAnnotationMemberValue::getText)
.forEach(Collectors.toList())
}
}
public class BasicSteps {
@Step("Открываем главную страницу")
public void openMainPage() { ... }
@Step("Открываем страницу машины марки {mark}")
public void openAutoCardPage(String mark) { ... }
@Step("Добавляем заметку {text} к машине")
public void addNotesToAutoCard(String text) { ... }
private void utilityMethod { ... }
}
Получили реальные методы
конкретный PsiMethod
Добываем список шагов
public class ScenarioExportAction extends AnAction {
public List<String> getSteps(PsiMethod testMethod) {
Arrays.stream(method.getBody().getStatements())
.filter(PsiMethodCallExpression.class::isInstance)
.map(PsiMethodClassExpression.class::cast)
.map(PsiMethodClassExpression::resolveMethod())
.filter(m -> m.hasAnnotation("...Step"))
.map(m -> m.getAnnotation("...Step"))
.map(a -> a.findDeclaredAttribute("value"))
.map(PsiAnnotationMemberValue::getText)
.forEach(Collectors.toList())
}
}
Получили текст аннотации
public class BasicSteps {
@Step("Открываем главную страницу")
public void openMainPage() { ... }
@Step("Открываем страницу машины марки {mark}")
public void openAutoCardPage(String mark) { ... }
@Step("Добавляем заметку {text} к машине")
public void addNotesToAutoCard(String text) { ... }
private void utilityMethod { ... }
}
Добываем список Шагов
public class ScenarioExportAction extends AnAction {
public List<String> getSteps(PsiMethod testMethod) {
Arrays.stream(method.getBody().getStatements())
.filter(PsiMethodCallExpression.class::isInstance)
.map(PsiMethodClassExpression.class::cast)
.map(PsiMethodClassExpression::resolveMethod())
.filter(m -> m.hasAnnotation("...Step"))
.map(m -> m.getAnnotation("...Step"))
.map(a -> a.findDeclaredAttribute("value"))
.map(PsiAnnotationMemberValue::getText)
.forEach(Collectors.toList())
}
}
Как сохранить результат?
public class TestCaseExportAction extends AnAction {
public void writeTestCases(Project project,
List<TestCase> testCases) {
String content = FreemarketUtls
.processTemplate("testcases.ftl", testCases);
Path projectDir = Paths.get(project.getBasePath())
Path indexFile = projectDir.resolve("index.html");
Files.write(indexFile, content.getBytes());
}
}
Шаблон "testcases.ftl"
<#list testcases as testcase>
<h2>${testcase.id} - ${testcase.name}</h2>
<div class="meta">
<div class="features">${testcase.features}</div>
</div>
<ul class="list-group">
<#list testcase.steps as step>
<li class="list-group-item">${step}</li>
</#list>
</ul
</#list>
Заполняем шаблон данными
public class TestCaseExportAction extends AnAction {
public void writeTestCases(Project project,
List<TestCase> testCases) {
String content = FreemarketUtls
.processTemplate("testcases.ftl", testCases);
Path projectDir = Paths.get(project.getBasePath())
Path indexFile = projectDir.resolve("index.html");
Files.write(indexFile, content.getBytes());
}
}
Сохраняем результат
public class TestCaseExportAction extends AnAction {
public void writeTestCases(Project project,
List<TestCase> testCases) {
String content = FreemarketUtls
.processTemplate("testcases.ftl", testCases);
Path projectDir = Paths.get(project.getBasePath())
Path indexFile = projectDir.resolve("index.html");
Files.write(indexFile, content.getBytes());
}
}
Что еще можно прокачать?
Проект на основе тестов
Как будем действовать?
Код метода
автотеста
Соберем
все
данные
Экспорт
в Jira
Как сохранить результат?
public class TestCaseExportAction extends AnAction {
public void writeToJira(Map<PsiMethod, TestCase> map) {
map.forEach((method, testCase) -> {
JiraIssue new = convert(testCase);
JiraIssue created = jiraClient.create(new);
String key = created.getKey();
addTmsLink(method, key);
});
}
}
Конвертируем в JiraIssue
public class TestCaseExportAction extends AnAction {
public void writeToJira(Map<PsiMethod, TestCase> map) {
map.forEach((method, testCase) -> {
JiraIssue new = convert(testCase);
JiraIssue created = jiraClient.create(new);
String key = created.getKey();
addTmsLink(method, key);
});
}
}
Конвертируем в JiraIssue
public class TestCaseExportAction extends AnAction {
public JiraIssue convert(TestCase testCase) {
return new JiraIssue()
.setSummary(testCase.getName())
.setDescription(toScenario(testCase.getSteps()))
.setFeatures(testCase.getFeatures())
.setStories(testCase.getStories())
.setProject(new Project().setKey("NEW"))
}
}
Создаем Issue в проекте
public class TestCaseExportAction extends AnAction {
public void writeToJira(Map<PsiMethod, TestCase> map) {
map.forEach((method, testCase) -> {
JiraIssue new = convert(testCase);
JiraIssue created = jiraClient.create(new);
String key = created.getKey();
addTmsLink(method, key);
});
}
}
Проставляем @TmsLink
public class TestCaseExportAction extends AnAction {
public void writeToJira(Map<PsiMethod, TestCase> map) {
map.forEach((method, testCase) -> {
JiraIssue new = convert(testCase);
JiraIssue created = jiraClient.create(new);
String key = created.getKey();
addTmsLink(method, key);
});
}
}
Какие еще есть идеи?
Экспорт в другие системы
Ревью автотестов
Импорт в проект
Экспорт из проекта
Рефакторинг проекта
Плагин за 10 минут
Вершина айсберга
Совершенствовать орудие труда
Собери свою коллекцию
Артем
Ерошенко
@eroshenkoam
https://github.com/eroshenkoam/idea-plugin-sample
Иван
Варивода
gh:varivoda
https://github.com/varivoda/idea-plugin-example
Вопросы?

More Related Content

What's hot

Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до ЯБыстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Andrey Bibichev
 
03 - Java. Объекты, классы и пакеты в Java
03 - Java. Объекты, классы и пакеты в Java03 - Java. Объекты, классы и пакеты в Java
03 - Java. Объекты, классы и пакеты в Java
Roman Brovko
 
08 - Java. Java-классы: взгляд изнутри
08 - Java. Java-классы: взгляд изнутри08 - Java. Java-классы: взгляд изнутри
08 - Java. Java-классы: взгляд изнутри
Roman Brovko
 
SECON'2016. Иовлев Роман, JDI is UI Automation Future
SECON'2016. Иовлев Роман, JDI is UI Automation FutureSECON'2016. Иовлев Роман, JDI is UI Automation Future
SECON'2016. Иовлев Роман, JDI is UI Automation Future
SECON
 
Agile Instrumentation
Agile InstrumentationAgile Instrumentation
Agile Instrumentation
Mikalai_Kardash
 
Legacy: как победить в гонке (Joker)
Legacy: как победить в гонке (Joker)Legacy: как победить в гонке (Joker)
Legacy: как победить в гонке (Joker)
Victor_Cr
 
09 - Java. Тестирование Java-программ
09 - Java. Тестирование Java-программ09 - Java. Тестирование Java-программ
09 - Java. Тестирование Java-программ
Roman Brovko
 
C# Desktop. Занятие 02.
C# Desktop. Занятие 02.C# Desktop. Занятие 02.
C# Desktop. Занятие 02.
Igor Shkulipa
 

What's hot (8)

Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до ЯБыстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
 
03 - Java. Объекты, классы и пакеты в Java
03 - Java. Объекты, классы и пакеты в Java03 - Java. Объекты, классы и пакеты в Java
03 - Java. Объекты, классы и пакеты в Java
 
08 - Java. Java-классы: взгляд изнутри
08 - Java. Java-классы: взгляд изнутри08 - Java. Java-классы: взгляд изнутри
08 - Java. Java-классы: взгляд изнутри
 
SECON'2016. Иовлев Роман, JDI is UI Automation Future
SECON'2016. Иовлев Роман, JDI is UI Automation FutureSECON'2016. Иовлев Роман, JDI is UI Automation Future
SECON'2016. Иовлев Роман, JDI is UI Automation Future
 
Agile Instrumentation
Agile InstrumentationAgile Instrumentation
Agile Instrumentation
 
Legacy: как победить в гонке (Joker)
Legacy: как победить в гонке (Joker)Legacy: как победить в гонке (Joker)
Legacy: как победить в гонке (Joker)
 
09 - Java. Тестирование Java-программ
09 - Java. Тестирование Java-программ09 - Java. Тестирование Java-программ
09 - Java. Тестирование Java-программ
 
C# Desktop. Занятие 02.
C# Desktop. Занятие 02.C# Desktop. Занятие 02.
C# Desktop. Занятие 02.
 

Similar to How IntelliJ IDEA plugin can help you in QA Automation

вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNGвебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNGAndrey Rebrov
 
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Andrey Rebrov
 
Mobile automation uamobile
Mobile automation uamobileMobile automation uamobile
Mobile automation uamobileUA Mobile
 
iOS and Android Mobile Test Automation
iOS and Android Mobile Test AutomationiOS and Android Mobile Test Automation
iOS and Android Mobile Test Automation
Andrii Dzynia
 
Java/Scala Lab: Юрий Литвиненко - Lightning talk
Java/Scala Lab: Юрий Литвиненко - Lightning talkJava/Scala Lab: Юрий Литвиненко - Lightning talk
Java/Scala Lab: Юрий Литвиненко - Lightning talkGeeksLab Odessa
 
QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...
QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...
QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...
QAFest
 
Плюсы и минусы автоматизации, пример из жизни
Плюсы и минусы автоматизации, пример из жизниПлюсы и минусы автоматизации, пример из жизни
Плюсы и минусы автоматизации, пример из жизни
z-tech
 
Let’s talk about Atlas
Let’s talk about AtlasLet’s talk about Atlas
Let’s talk about Atlas
Artem Sokovets
 
Статический анализ кода в DDD
Статический анализ кода в DDDСтатический анализ кода в DDD
Статический анализ кода в DDD
Aleksei Alekseev
 
2014 iForum - Grails in Startup
2014 iForum - Grails in Startup2014 iForum - Grails in Startup
2014 iForum - Grails in StartupBohdan Danyliuk
 
Кирилл Харьков
Кирилл ХарьковКирилл Харьков
Кирилл Харьков
CodeFest
 
Разработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительностьРазработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительность
Вадим Воробьев
 
Елена Жукова "Жизнь вне JavaScript"
Елена Жукова "Жизнь вне JavaScript"Елена Жукова "Жизнь вне JavaScript"
Елена Жукова "Жизнь вне JavaScript"
Fwdays
 
Delegates and events in C#
Delegates and events in C#Delegates and events in C#
Delegates and events in C#
Mikhail Shcherbakov
 
Pycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и DjangoPycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и DjangoIlya Shalyapin
 
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
it-people
 
Разработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconruРазработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconru
JetStyle
 
"Погружение в Robolectric" Дмитрий Костырев (Avito)
"Погружение в Robolectric"  Дмитрий Костырев (Avito)"Погружение в Robolectric"  Дмитрий Костырев (Avito)
"Погружение в Robolectric" Дмитрий Костырев (Avito)
AvitoTech
 
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
HappyDev
 
Сергей Константинов — Что интересного готовит нам W3C
Сергей Константинов — Что интересного готовит нам W3CСергей Константинов — Что интересного готовит нам W3C
Сергей Константинов — Что интересного готовит нам W3C
Yandex
 

Similar to How IntelliJ IDEA plugin can help you in QA Automation (20)

вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNGвебинар - функциональное тестирование с использованием Selenium 2 и TestNG
вебинар - функциональное тестирование с использованием Selenium 2 и TestNG
 
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
 
Mobile automation uamobile
Mobile automation uamobileMobile automation uamobile
Mobile automation uamobile
 
iOS and Android Mobile Test Automation
iOS and Android Mobile Test AutomationiOS and Android Mobile Test Automation
iOS and Android Mobile Test Automation
 
Java/Scala Lab: Юрий Литвиненко - Lightning talk
Java/Scala Lab: Юрий Литвиненко - Lightning talkJava/Scala Lab: Юрий Литвиненко - Lightning talk
Java/Scala Lab: Юрий Литвиненко - Lightning talk
 
QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...
QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...
QA Fest 2017. Яна Кокряшкина. Интеграция автоматизированных тестов с инструме...
 
Плюсы и минусы автоматизации, пример из жизни
Плюсы и минусы автоматизации, пример из жизниПлюсы и минусы автоматизации, пример из жизни
Плюсы и минусы автоматизации, пример из жизни
 
Let’s talk about Atlas
Let’s talk about AtlasLet’s talk about Atlas
Let’s talk about Atlas
 
Статический анализ кода в DDD
Статический анализ кода в DDDСтатический анализ кода в DDD
Статический анализ кода в DDD
 
2014 iForum - Grails in Startup
2014 iForum - Grails in Startup2014 iForum - Grails in Startup
2014 iForum - Grails in Startup
 
Кирилл Харьков
Кирилл ХарьковКирилл Харьков
Кирилл Харьков
 
Разработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительностьРазработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительность
 
Елена Жукова "Жизнь вне JavaScript"
Елена Жукова "Жизнь вне JavaScript"Елена Жукова "Жизнь вне JavaScript"
Елена Жукова "Жизнь вне JavaScript"
 
Delegates and events in C#
Delegates and events in C#Delegates and events in C#
Delegates and events in C#
 
Pycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и DjangoPycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и Django
 
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
Илья Шаляпин, Евгений Генералов: Разработка через тестирование в Python и Djn...
 
Разработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconruРазработка через тестирование в Python и Django #pyconru
Разработка через тестирование в Python и Django #pyconru
 
"Погружение в Robolectric" Дмитрий Костырев (Avito)
"Погружение в Robolectric"  Дмитрий Костырев (Avito)"Погружение в Robolectric"  Дмитрий Костырев (Avito)
"Погружение в Robolectric" Дмитрий Костырев (Avito)
 
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
2015-12-06 Максим Юнусов - Проектирование REST приложения, или нужно ли прогр...
 
Сергей Константинов — Что интересного готовит нам W3C
Сергей Константинов — Что интересного готовит нам W3CСергей Константинов — Что интересного готовит нам W3C
Сергей Константинов — Что интересного готовит нам W3C
 

More from Dmitriy Gumeniuk

Building functional Quality Gates with ReportPortal
Building functional Quality Gates with ReportPortalBuilding functional Quality Gates with ReportPortal
Building functional Quality Gates with ReportPortal
Dmitriy Gumeniuk
 
Self healing test automation with Healenium and Minimization of regression su...
Self healing test automation with Healenium and Minimization of regression su...Self healing test automation with Healenium and Minimization of regression su...
Self healing test automation with Healenium and Minimization of regression su...
Dmitriy Gumeniuk
 
Test Gap Analysis and regression minimization with Drill4j. Observability on ...
Test Gap Analysis and regression minimization with Drill4j. Observability on ...Test Gap Analysis and regression minimization with Drill4j. Observability on ...
Test Gap Analysis and regression minimization with Drill4j. Observability on ...
Dmitriy Gumeniuk
 
What's new in selenium grid 4.0 expected
What's new in selenium grid 4.0 expectedWhat's new in selenium grid 4.0 expected
What's new in selenium grid 4.0 expected
Dmitriy Gumeniuk
 
Automation of Security scanning easy or cheese?
Automation of Security scanning easy or cheese?Automation of Security scanning easy or cheese?
Automation of Security scanning easy or cheese?
Dmitriy Gumeniuk
 
Device manager server for android farm or running more than 30k tests every day
Device manager server for android farm or running more than 30k tests every dayDevice manager server for android farm or running more than 30k tests every day
Device manager server for android farm or running more than 30k tests every day
Dmitriy Gumeniuk
 
Migrating to the serverless mindset
Migrating to the serverless mindsetMigrating to the serverless mindset
Migrating to the serverless mindset
Dmitriy Gumeniuk
 
Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)
Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)
Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)
Dmitriy Gumeniuk
 
ReportPortal use cases presentation
 ReportPortal use cases presentation ReportPortal use cases presentation
ReportPortal use cases presentation
Dmitriy Gumeniuk
 
Applicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, Minsk
Applicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, MinskApplicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, Minsk
Applicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, Minsk
Dmitriy Gumeniuk
 
ReportPortal.io - how to make machine learning categorize your test fails
ReportPortal.io - how to make machine learning categorize your test failsReportPortal.io - how to make machine learning categorize your test fails
ReportPortal.io - how to make machine learning categorize your test fails
Dmitriy Gumeniuk
 

More from Dmitriy Gumeniuk (11)

Building functional Quality Gates with ReportPortal
Building functional Quality Gates with ReportPortalBuilding functional Quality Gates with ReportPortal
Building functional Quality Gates with ReportPortal
 
Self healing test automation with Healenium and Minimization of regression su...
Self healing test automation with Healenium and Minimization of regression su...Self healing test automation with Healenium and Minimization of regression su...
Self healing test automation with Healenium and Minimization of regression su...
 
Test Gap Analysis and regression minimization with Drill4j. Observability on ...
Test Gap Analysis and regression minimization with Drill4j. Observability on ...Test Gap Analysis and regression minimization with Drill4j. Observability on ...
Test Gap Analysis and regression minimization with Drill4j. Observability on ...
 
What's new in selenium grid 4.0 expected
What's new in selenium grid 4.0 expectedWhat's new in selenium grid 4.0 expected
What's new in selenium grid 4.0 expected
 
Automation of Security scanning easy or cheese?
Automation of Security scanning easy or cheese?Automation of Security scanning easy or cheese?
Automation of Security scanning easy or cheese?
 
Device manager server for android farm or running more than 30k tests every day
Device manager server for android farm or running more than 30k tests every dayDevice manager server for android farm or running more than 30k tests every day
Device manager server for android farm or running more than 30k tests every day
 
Migrating to the serverless mindset
Migrating to the serverless mindsetMigrating to the serverless mindset
Migrating to the serverless mindset
 
Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)
Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)
Building a self-service marketplace for Test Data (Dzmitry Humianiuk, Belarus)
 
ReportPortal use cases presentation
 ReportPortal use cases presentation ReportPortal use cases presentation
ReportPortal use cases presentation
 
Applicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, Minsk
Applicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, MinskApplicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, Minsk
Applicabilitity of Machine Learning in Test Automation @ Delex Conf 2018, Minsk
 
ReportPortal.io - how to make machine learning categorize your test fails
ReportPortal.io - how to make machine learning categorize your test failsReportPortal.io - how to make machine learning categorize your test fails
ReportPortal.io - how to make machine learning categorize your test fails
 

How IntelliJ IDEA plugin can help you in QA Automation

  • 1. How Idea plugin can help you in QA Automation Delex Conf, Minsk, 2019
  • 4. Приложение для управления проектами Более 17 000 компаний из 120 стран Google,AirBnb,Adobe,Western Union, …
  • 12. Расставить все по местамКак обеспечить равномерность?
  • 17. Я видел много инструментов
  • 19. Тесты катать - не ракеты строить
  • 20. Тесты катать - не ракеты строить Атомарные Похожие Простые
  • 21. Доктор, я перевожу тесты на jUnit5 силой мысли, это нормально?
  • 24. Импорт в проект Экспорт из проекта Рефакторинг проекта Плагин за 10 минут
  • 25. Импорт в проект Экспорт из проекта Рефакторинг проекта Плагин за 10 минут
  • 27. New > Plugin DevKit > Action
  • 35. Точка входа в Idea Plugin public class AnAction { @Override public void actionPerformed(AnActionEvent event) { //MAGIC here } }
  • 36. public class FirstAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { Project project = e.getProject(); Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); PsiElement psiElement = e.getData(PSI_ELEMENT); String name = ((PsiClass)psiElement).getQualifiedName(); showDialog(cName); } } Основа всего PsiElement
  • 37. public class Steps { @Step("Open Wrike login page") public void openWrikeLogin() { ... } @Step("Open inbox") public void openInbox() { ... } ... } PsiClass - работа с классом
  • 38. PsiMethod - работа с методами public class Steps { @Step("Open Wrike login page") public void openWrikeLogin() { ... } @Step("Open inbox") public void openInbox() { ... } ... }
  • 39. PsiAnnotation - аннотации public class Steps { @Step("Open Wrike login page") public void openWrikeLogin() { ... } @Step("Open inbox") public void openInbox() { ... } ... }
  • 40. public class FirstAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { PsiElement psiElement = e.getData(PSI_ELEMENT); PsiClass psiClass = ((PsiClass)psiElement); String name = psiClass.getQualifiedName(); showDialog(name); } } Выведем Qualified Name
  • 44.
  • 45.
  • 46. Импорт в проект Экспорт из проекта Рефакторинг проекта Плагин за 10 минут
  • 48.
  • 49.
  • 50.
  • 51. 10 K селениум тестов
  • 52. Рассмотрим обычный тест public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 53. Категории автотеста public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 54. На самом деле их много public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class, Workspace.class, IE.class, Safari.class ...}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 55. Технические категории public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 56. Продуктовые категории public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 57. Нужно поменять разметку public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 58. Добавить продуктовые фичи public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @Feature("Login") @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 59. Добавить технические типы public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @Feature("Login") @Types({@Type("Firefox"), @Type("Smoke")}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 60. Удалить ненужные категории public class LoginTest { @Before() public void prepare() { ... } @Test @Feature("Login") @Types({@Type("Firefox"), @Type("Smoke")}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 61. Какую проблему решаем?Способ решения проблемы
  • 63. ReplaceCategoryAction public class ReplaceCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT) if (element instance of PsiClass) { replaceCategory((PsiClass)element) } } }
  • 64. Получили PsiClass public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } }
  • 65. Берем все методы класса private void replaceCategory(PsiClass psiClass) { Arrays.stream(psiClass.getMethods()) .filter(m -> m.hasAnnotation("...Test")) .filter(m -> m.hasAnnotation("...Category")) .forEach(this::replaceCategory); }
  • 66. public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } } Получили все методы класса
  • 67. Фильтруем нужные методы private void replaceCategory(PsiClass psiClass) { Arrays.stream(psiClass.getMethods()) .filter(m -> m.hasAnnotation("...Test")) .filter(m -> m.hasAnnotation("...Category")) .forEach(this::replaceCategory); }
  • 68. Получили тестовый метод public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } }
  • 69. Берем текст @Category private void replaceCategory(PsiMethod method) { PsiAnnotation category = method.getAnnotation("org...Category"); String categoryValues = category. findDeclaredAttributeValue(“value”).getText(); //next slide plz }
  • 70. public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } Получили текст @Category
  • 71. Технические и продуктовые private void replaceCategory(PsiMethod method) { String categoryValues = ... String featureValue = findFeatureValue(categoryValues) List<String> typeValues = findTypeValues(categoryValues) //next slide plz }
  • 72. public class LoginTest { @Before() public void prepare() { ... } @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } } Технические и продуктовые
  • 73. Добавляем @Feature private void replaceCategory(PsiMethod method) { String featureValue = ... List<String> typeValues = ... addFeature(method, featureValue); addTypes(method, typeValues); removeCategory(method); }
  • 74. Добавляем аннотацию void addFeature(PsiMethod method, String feature) { String body = "io...Feature("" + feature + "")"; PsiAnnotation featureAnnotation = method.getModifierList().addAnnotation(body); JavaCodeStyleManager.getInstance(project) .shortenClassReferences(featureAnnotation); } @io.qameta.Feature("Login")
  • 75. Добавили с полным путем public class LoginTest { @Before() public void prepare() { ... } @io.qameta.allure.Feature("Login") @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } }
  • 76. Оптимизируем импорты void addFeature(PsiMethod method, String feature) { String body = "io...Feature("" + feature + "")"; PsiAnnotation featureAnnotation = method.getModifierList().addAnnotation(body); JavaCodeStyleManager.getInstance(project) .shortenClassReferences(featureAnnotation); } @Feature("Login")
  • 77. Оптимизировали импорты public class LoginTest { @Before() public void prepare() { ... } @Feature("Login") @Test @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } }
  • 78. Нельзя просто взять и изменить код!
  • 79. private void replaceCategory(PsiMethod psiMethod) { CommandProcessor.getInstance().executeCommand(()->{ getApplication().runWriteAction(()->{ //код с предыдущего слайда }) }) } Обертка над изменением кода
  • 80. Добавляем @Types private void replaceCategory(PsiMethod method) { String featureValue = ... List<String> typeValues = ... addFeature(method, featureValue); addTypes(method, typeValues); removeCategory(method); }
  • 81. Все по накатанной схеме void addTypes(PsiMethod method, List<String> types) { String body = createTypesBody(types); PsiAnnotation typesAnnotation = method.getModifierList().addAnnotation(body); JavaCodeStyleManager.getInstance(project) .shortenClassReferences(featureAnnotation); } @Types({@Type("Firefox"), @Type("Smoke")})
  • 82. Аннотация в виде строки public String createTypesBody(List<String> types) { return types.stream() .map(f -> "@io..Type("" + f + "")") .collect(Collectors.joining(",")) } public String getTypesText(List<String> types) { String inner = getInnerFeaturesText(features); return "@io..Types({" + inner + "})"; } @Types({@Type("Firefox"), @Type("Smoke")})
  • 83. Добавили @Types public class LoginTest { @Before() public void prepare() { ... } @Feature("Login") @Test @Types({@Type("Firefox"), @Type("Smoke")}) @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } }
  • 84. Удаляем @Category private void replaceCategory(PsiMethod method) { String featureValue = ... List<String> typeValues = ... addFeature(method, featureValue); addTypes(method, typeValues); removeCategory(method); }
  • 85. public class LoginTest { @Before() public void prepare() { ... } @Feature("Login") @Test @Types({@Type("Firefox"), @Type("Smoke")}) @Category({Firefox.class, Smoke.class, Login.class}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... } Удаляем @Category
  • 86. private void deleteCategory(PsiMethod method) { PsiAnnotation category = method .getModifierList() .findAnnotation("org...Category"); category.delete(); } Удаляем @Category
  • 87. Удалили @Category public class LoginTest { @Before() public void prepare() { ... } @Feature("Login") @Test @Types({@Type("Firefox"), @Type("Smoke")}) @DisplayName("Check login") public void loginWithCorrectPassword() { ... }
  • 88.
  • 89.
  • 90. А для всех тестов?
  • 91. ReplaceAllCategoryAction public class ReplaceAllCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { project = e.getProject(); AllClassesGetter.processJavaClasses( new PlainPrefixMatcher(""), project, GlobalSearchScope.allScope(project), processor); } }
  • 92. Начинается с "Test" public class ReplaceAllCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { project = e.getProject(); AllClassesGetter.processJavaClasses( new PlainPrefixMatcher("Test"), project, GlobalSearchScope.allScope(project), processor); } }
  • 93. Обходим все классы public class ReplaceAllCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { project = e.getProject(); AllClassesGetter.processJavaClasses( new PlainPrefixMatcher(""), project, GlobalSearchScope.allScope(project), processor); } }
  • 94. Указываем проект public class ReplaceAllCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { project = e.getProject(); AllClassesGetter.processJavaClasses( new PlainPrefixMatcher(""), project, GlobalSearchScope.allScope(project), processor); } }
  • 95. Скоуп поиска public class ReplaceAllCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { project = e.getProject(); AllClassesGetter.processJavaClasses( new PlainPrefixMatcher(""), project, GlobalSearchScope.allScope(project), processor); } }
  • 96. Логика обработки public class ReplaceAllCategoryAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { project = e.getProject(); AllClassesGetter.processJavaClasses( new PlainPrefixMatcher(""), project, GlobalSearchScope.allScope(project), processor); } }
  • 97. Метод используемый выше Processor<PsiClass> processor = psiClass -> { replaceCategory(psiClass); return true; };
  • 98.
  • 99.
  • 100. Какие еще есть идеи? Разметка новых тестов Переезд на новую версию
  • 101. Импорт в проект Экспорт из проекта Рефакторинг проекта Плагин за 10 минут
  • 105. public class AddFavoritesAfterNoteTest { @Test @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } } Возьмем название теста
  • 107. Нужно расставить ссылки public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @TmsLink("AE-4") @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } }
  • 109. Какую проблему решаем?Способ решения проблемы
  • 111. Работаем только с классами public class JiraKeyImportAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT) if (element instance of PsiClass) { addTmsLinkToClassMethods((PsiClass)element) } } }
  • 112. Выбрали класс для апдейта public class AddFavoritesAfterNoteTest { @Test @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } private void utilityMethod() { ... } }
  • 113. public class JiraKeyImportAction extends AnAction { void addTmsLinkToClassMethods(PsiClass testClass) { Arrays.stream(testClass.getMethods()) .filter(m -> m.hasAnnotation("org..Test")) .filter(m -> m.hasAnnotation("org..DisplayName")) .forEach(this::addTmsLinkToMethod) } } Получаем все методы класса
  • 114. public class JiraKeyImportAction extends AnAction { void addTmsLinkToClassMethods(PsiClass testClass) { Arrays.stream(testClass.getMethods()) .filter(m -> m.hasAnnotation("org..Test")) .filter(m -> m.hasAnnotation("org..DisplayName")) .forEach(this::addTmsLinkToMethod) } } Фильтруем тестовые методы
  • 115. Выбрали методы для апдейта public class AddFavoritesAfterNoteTest { @Test @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } private void utilityMethod() { ... } }
  • 116. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod method) { PsiAnnotatation nameAnnotation = method.getAnnotation("org..DisplayName"); String text = name .findDeclaredAttributeValue("value").getText() String key = jiraClient.findByText(text); addTmsLinkToMethod(testMethod, key); } } Берем аннотацию DisplayName
  • 117. Нашли аннотацию с именем public class AddFavoritesAfterNoteTest { @Test @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } private void utilityMethod() { ... } }
  • 118. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod testMethod) { PsiAnnotatation name = method.getAnnotation("org..DisplayName"); String text = name .findDeclaredAttributeValue("value").getText() String key = jiraClient.findByText(text); addTmsLinkToMethod(testMethod, key); } } Берем текст DisplayName
  • 119. Тест для поиска в Jira public class AddFavoritesAfterNoteTest { @Test @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } private void utilityMethod() { ... } }
  • 120. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod testMethod) { PsiAnnotatation name = method.getAnnotation("org..DisplayName"); String text = name .findDeclaredAttributeValue("value").getText() String key = jiraClient.findByText(text); addTmsLinkToMethod(testMethod, key); } } Ищем Issue по тексту в Jira
  • 123. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod testMethod) { PsiAnnotatation name = method.getAnnotation("org..DisplayName"); String text = name .findDeclaredAttributeValue("value").getText() String key = jiraClient.findByText(text); addTmsLinkToMethod(testMethod, key); } } Добавляем аннотацию в код
  • 124. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod method,String key){ String tmsLinksAnnotationText = "@io..TmsLink("" + key + "")"; PsiAnnotation tmsLink = method.addAnnotation(tmsLinkAnnotationText); JavaCodeStyleManager.getInstance(project) .shortenClassReferences(tmsLink); } } Формируем текст аннотации
  • 125. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod method,String key){ String tmsLinksAnnotationText = "@io..TmsLink("" + key + "")"; PsiAnnotation tmsLink = method.addAnnotation(tmsLinkAnnotationText); JavaCodeStyleManager.getInstance(project) .shortenClassReferences(tmsLink); } } Добавляем аннотацию
  • 126. Добавили аннотацию public class AddFavoritesAfterNoteTest { @Test @io.qameta.allure.TmsLink("AE-5") @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } }
  • 127. public class JiraKeyImportAction extends AnAction { void addTmsLinkToMethod(PsiMethod method,String key){ String tmsLinksAnnotationText = "@io..TmsLink("" + key + "")"; PsiAnnotation tmsLink = method.addAnnotation(tmsLinkAnnotationText); JavaCodeStyleManager.getInstance(project) .shortenClassReferences(tmsLink); } } Формируем текст аннотации
  • 128. Добавили импорт аннотации public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } }
  • 129.
  • 130.
  • 131. Что еще можно прокачать?
  • 134. Создаем JiraLabelsImportAction public class JiraLabelsImportAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT) if (element instance of PsiClass) { addTagsToClassMethods((PsiClass)element) } } }
  • 135. public class JiraLabelsImportAction extends AnAction { void addTagsToClassMethods(PsiClass testClass) { Arrays.stream(testClass.getMethods()) .filter(m -> m.hasAnnotation("org..TmsLink")) .forEach(this::addTmsLinkToMethod) } } Фильтруем методы с ключом
  • 136. public class JiraLabelsImportAction extends AnAction { void addTagsToMethod(PsiMethod testMethod) { PsiAnnotation tmsLink = testMethod.getAnnotation("io..TmsLink"); String key = getTextValue(tmsLink); List<String> labels = jiraClient.getLables(key); String tagsText = getTagsText(labels); PsiAnnotation tags = create(tagsText, testMethod); testMethod.getModifiersList() .addAfter(tags, tmsLink); } } Зачитываем ключ Issue в Jira
  • 137. Получили ключи тестов public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } @Test @TmsLink("AE-4") @DisplayName("Удаление из избранного после удаления заметки") public void shouldDeleteToFavoriteAfterNodeTest() { ... } }
  • 138. public class JiraLabelsImportAction extends AnAction { void addTagsToMethod(PsiMethod testMethod) { PsiAnnotation tmsLink = testMethod.getAnnotation("io..TmsLink"); String key = getTextValue(tmsLink); List<String> labels = jiraClient.getLables(key); String tagsText = getTagsText(labels); PsiAnnotation tags = create(tagsText, testMethod); testMethod.getModifiersList() .addAfter(tags, tmsLink); } } Забираем лейбочки из Jira
  • 140. public class JiraLabelsImportAction extends AnAction { void addTagsToMethod(PsiMethod testMethod) { PsiAnnotation tmsLink = testMethod.getAnnotation("io..TmsLink"); String key = getTextValue(tmsLink); List<String> labels = jiraClient.getLables(key); String tagsText = getTagsText(labels); PsiAnnotation tags = create(tagsText, testMethod); testMethod.getModifiersList() .addAfter(tags, tmsLink); } } Создаем строку с аннотацией
  • 141. Аннотация в виде строки public String getInnerTagsText(List<String> labels) { return features.stream() .map(f -> "@org..Tag("" + f + "")") .collect(Collectors.joining(",")) } public String getTagsText(List<String> labels) { String inner = getInnerTagsText(labels); return "@org..Tag({" + inner + "})"; } @org..Tags({@org..Tag("regress"), ...})
  • 142. public class JiraLabelsImportAction extends AnAction { void addTagsToMethod(PsiMethod testMethod) { PsiAnnotation tmsLink = testMethod.getAnnotation("io..TmsLink"); String key = getTextValue(tmsLink); List<String> labels = jiraClient.getLables(key); String tagsText = getTagsText(labels); PsiAnnotation tags = create(tagsText, testMethod); testMethod.getModifiersList() .addAfter(tags, tmsLink); } } Создаем аннотацию из строки
  • 143. public class JiraLabelsImportAction extends AnAction { void addTagsToMethod(PsiMethod testMethod) { PsiAnnotation tmsLink = testMethod.getAnnotation("io..TmsLink"); String key = getTextValue(tmsLink); List<String> labels = jiraClient.getLables(key); String tagsText = getTagsText(labels); PsiAnnotation tags = create(tagsText, testMethod); testMethod.getModifiersList() .addAfter(tags, tmsLink); } } @Tags после @TmsLink
  • 144. Добавили теги в тест public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @org..Tags({@org..Tag("regress")}) @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } }
  • 145. public class JiraLabelsImportAction extends AnAction { void addTagsToMethod(PsiMethod testMethod) { addImport(testMethod.getContainingFile(), "org..Tag"); addImport(testMethod.getContainingFile(), "org..Tags"); // код из предыдущего слайда с write операцией optimizeImports(testMethod.getContainingFile()); } } Оптимизируем импорты
  • 146. Добавили теги в тест public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @Tags({@Tag("regress")}) @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { ... } } Оптимизировали импорты
  • 147.
  • 148.
  • 149. Какие еще есть идеи? Импорт сценария теста Импорт Feature/Story Включение/выключение
  • 150. Импорт в проект Экспорт из проекта Рефакторинг проекта Плагин за 10 минут
  • 152. А что в тестах проверяется?
  • 154. Какую проблему решаем?Способ решения проблемы
  • 156. Как будем действовать? Код метода автотеста
  • 157. public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @Features({@Feature("Favorites")}) @Stories({@Story("Add favorites after note")}) @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { steps.openMainPage(); ... } } Мета информация
  • 158. public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @Features({@Feature("Favorites")}) @Stories({@Story("Add favorites after note")}) @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { steps.openMainPage(); ... } } Сценарий теста
  • 159. Как будем действовать? Код метода автотеста Соберем все данные
  • 160. Информация для экспорта public class TestCase implements Serializable { private String id; private String name; private List<String> features; private List<String> stories; private List<String> steps; ... }
  • 161. Как будем действовать? Код метода автотеста Соберем все данные Сохраним в удобном формате
  • 164. Создаем TestCaseExportAction public class TestCaseExportAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT); List<TestCase> testcases = Arrays.stream(((PsiClass) element).getMethods()) .filter(m -> m.hasAnnotation("org..Test")) .map(this::getTestCaseFromMethod) .collect(Collectors.toList()); writeTestCases(event.getProject(), testcases); } }
  • 165. Берем все методы тестов public class TestCaseExportAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT); List<TestCase> testcases = Arrays.stream(((PsiClass) element).getMethods()) .filter(m -> m.hasAnnotation("org..Test")) .map(this::getTestCaseFromMethod) .collect(Collectors.toList()); writeTestCases(event.getProject(), testcases); } }
  • 166. Собираем информацию public class TestCaseExportAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT); List<TestCase> testcases = Arrays.stream(((PsiClass) element).getMethods()) .filter(m -> m.hasAnnotation("org..Test")) .map(this::getTestCaseFromMethod) .collect(Collectors.toList()); writeTestCases(event.getProject(), testcases); } }
  • 167. Сохраняем результат public class TestCaseExportAction extends AnAction { @Override public void actionPerformed(AnActionEvent event) { PsiElement element = event.getData(PSI_ELEMENT); List<TestCase> testcases = Arrays.stream(((PsiClass) element).getMethods()) .filter(m -> m.hasAnnotation("org..Test")) .map(this::getTestCaseFromMethod) .collect(Collectors.toList()); writeTestCases(event.getProject(), testcases); } }
  • 168. Как собираем данные метода? public class TestCaseExportAction extends AnAction { public TestCase getTestCaseFromMethod(PsiMethod method) { TestCase testCase = new TestCase(); testCase.setId(getTmsLinkText(method)); testCase.setName(getDisplayNameText(method)); testCase.setFeature(getFeaturesText(method)); testCase.setStories(getStoriesText(method)); testCase.setSteps(getSteps(method)); return testCase; } }
  • 169. Уже делали раньше public class TestCaseExportAction extends AnAction { public TestCase getTestCaseFromMethod(PsiMethod method) { TestCase testCase = new TestCase(); testCase.setId(getTmsLinkText(method)); testCase.setName(getDisplayNameText(method)); testCase.setFeature(getFeaturesText(method)); testCase.setStories(getStoriesText(method)); testCase.setSteps(getSteps(method)); return testCase; } }
  • 170. public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @Features({@Feature("Favorites")}) @Stories({@Story("Add favorites after note")}) @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { steps.openMainPage(); ... } } Уже делали раньше
  • 171. Тоже самое, только массивы public class TestCaseExportAction extends AnAction { public TestCase getTestCaseFromMethod(PsiMethod method) { TestCase testCase = new TestCase(); testCase.setId(getTmsLinkText(method)); testCase.setName(getDisplayNameText(method)); testCase.setFeature(getFeaturesText(method)); testCase.setStories(getStoriesText(method)); testCase.setSteps(getSteps(method)); return testCase; } }
  • 172. public class AddFavoritesAfterNoteTest { @Test @TmsLink("AE-5") @Features({@Feature("Favorites")}) @Stories({@Story("Add favorites after note")}) @DisplayName("Добавление в избранное после создания заметки") public void shouldAddToFavoriteAfterNodeTest() { steps.openMainPage(); ... } } Тоже самое, только массивы
  • 173. А вот это не тривиально public class TestCaseExportAction extends AnAction { public TestCase getTestCaseFromMethod(PsiMethod method) { TestCase testCase = new TestCase(); testCase.setId(getTmsLinkText(method)); testCase.setName(getDisplayNameText(method)); testCase.setFeature(getFeaturesText(method)); testCase.setStories(getStoriesText(method)); testCase.setSteps(getSteps(method)); return testCase; } }
  • 174. public class AddFavoritesAfterNoteTest { @Test public void shouldAddToFavoriteAfterNodeTest() { steps.openMainPage(); steps.openAutoCardPage("Volvo XC90") steps.addNotesToAutoCard("в хорошем состоянии"); steps.openFavoritesPage("в хорошем состоянии"); steps.checkFavoritesListContains("Volvo XC90") } } Сценарий теста
  • 175. Добываем список Шагов public class ScenarioExportAction extends AnAction { public List<String> getSteps(PsiMethod testMethod) { Arrays.stream(method.getBody().getStatements()) .filter(PsiMethodCallExpression.class::isInstance) .map(PsiMethodClassExpression.class::cast) .map(PsiMethodClassExpression::resolveMethod()) .filter(m -> m.hasAnnotation("...Step")) .map(m -> m.getAnnotation("...Step")) .map(a -> a.findDeclaredAttribute("value")) .map(PsiAnnotationMemberValue::getText) .forEach(Collectors.toList()) } }
  • 176. public class AddFavoritesAfterNoteTest { @Test public void shouldAddToFavoriteAfterNodeTest() { steps.openMainPage(); steps.openAutoCardPage("Volvo XC90") steps.addNotesToAutoCard("в хорошем состоянии"); steps.openFavoritesPage(); steps.checkFavoritesListContains("Volvo XC90") } } Не метод, а оператор вызова PsiMethodCallExpression
  • 177. Добываем список методов public class ScenarioExportAction extends AnAction { public List<String> getSteps(PsiMethod testMethod) { Arrays.stream(method.getBody().getStatements()) .filter(PsiMethodCallExpression.class::isInstance) .map(PsiMethodClassExpression.class::cast) .map(PsiMethodClassExpression::resolveMethod) .filter(m -> m.hasAnnotation("...Step")) .map(m -> m.getAnnotation("...Step")) .map(a -> a.findDeclaredAttribute("value")) .map(PsiAnnotationMemberValue::getText) .forEach(Collectors.toList()) } }
  • 178. public class BasicSteps { @Step("Открываем главную страницу") public void openMainPage() { ... } @Step("Открываем страницу машины марки {mark}") public void openAutoCardPage(String mark) { ... } @Step("Добавляем заметку {text} к машине") public void addNotesToAutoCard(String text) { ... } private void utilityMethod { ... } } Получили реальные методы конкретный PsiMethod
  • 179. Добываем список шагов public class ScenarioExportAction extends AnAction { public List<String> getSteps(PsiMethod testMethod) { Arrays.stream(method.getBody().getStatements()) .filter(PsiMethodCallExpression.class::isInstance) .map(PsiMethodClassExpression.class::cast) .map(PsiMethodClassExpression::resolveMethod()) .filter(m -> m.hasAnnotation("...Step")) .map(m -> m.getAnnotation("...Step")) .map(a -> a.findDeclaredAttribute("value")) .map(PsiAnnotationMemberValue::getText) .forEach(Collectors.toList()) } }
  • 180. Получили текст аннотации public class BasicSteps { @Step("Открываем главную страницу") public void openMainPage() { ... } @Step("Открываем страницу машины марки {mark}") public void openAutoCardPage(String mark) { ... } @Step("Добавляем заметку {text} к машине") public void addNotesToAutoCard(String text) { ... } private void utilityMethod { ... } }
  • 181. Добываем список Шагов public class ScenarioExportAction extends AnAction { public List<String> getSteps(PsiMethod testMethod) { Arrays.stream(method.getBody().getStatements()) .filter(PsiMethodCallExpression.class::isInstance) .map(PsiMethodClassExpression.class::cast) .map(PsiMethodClassExpression::resolveMethod()) .filter(m -> m.hasAnnotation("...Step")) .map(m -> m.getAnnotation("...Step")) .map(a -> a.findDeclaredAttribute("value")) .map(PsiAnnotationMemberValue::getText) .forEach(Collectors.toList()) } }
  • 182. Как сохранить результат? public class TestCaseExportAction extends AnAction { public void writeTestCases(Project project, List<TestCase> testCases) { String content = FreemarketUtls .processTemplate("testcases.ftl", testCases); Path projectDir = Paths.get(project.getBasePath()) Path indexFile = projectDir.resolve("index.html"); Files.write(indexFile, content.getBytes()); } }
  • 183. Шаблон "testcases.ftl" <#list testcases as testcase> <h2>${testcase.id} - ${testcase.name}</h2> <div class="meta"> <div class="features">${testcase.features}</div> </div> <ul class="list-group"> <#list testcase.steps as step> <li class="list-group-item">${step}</li> </#list> </ul </#list>
  • 184. Заполняем шаблон данными public class TestCaseExportAction extends AnAction { public void writeTestCases(Project project, List<TestCase> testCases) { String content = FreemarketUtls .processTemplate("testcases.ftl", testCases); Path projectDir = Paths.get(project.getBasePath()) Path indexFile = projectDir.resolve("index.html"); Files.write(indexFile, content.getBytes()); } }
  • 185. Сохраняем результат public class TestCaseExportAction extends AnAction { public void writeTestCases(Project project, List<TestCase> testCases) { String content = FreemarketUtls .processTemplate("testcases.ftl", testCases); Path projectDir = Paths.get(project.getBasePath()) Path indexFile = projectDir.resolve("index.html"); Files.write(indexFile, content.getBytes()); } }
  • 186.
  • 187.
  • 188. Что еще можно прокачать?
  • 190. Как будем действовать? Код метода автотеста Соберем все данные Экспорт в Jira
  • 191. Как сохранить результат? public class TestCaseExportAction extends AnAction { public void writeToJira(Map<PsiMethod, TestCase> map) { map.forEach((method, testCase) -> { JiraIssue new = convert(testCase); JiraIssue created = jiraClient.create(new); String key = created.getKey(); addTmsLink(method, key); }); } }
  • 192. Конвертируем в JiraIssue public class TestCaseExportAction extends AnAction { public void writeToJira(Map<PsiMethod, TestCase> map) { map.forEach((method, testCase) -> { JiraIssue new = convert(testCase); JiraIssue created = jiraClient.create(new); String key = created.getKey(); addTmsLink(method, key); }); } }
  • 193. Конвертируем в JiraIssue public class TestCaseExportAction extends AnAction { public JiraIssue convert(TestCase testCase) { return new JiraIssue() .setSummary(testCase.getName()) .setDescription(toScenario(testCase.getSteps())) .setFeatures(testCase.getFeatures()) .setStories(testCase.getStories()) .setProject(new Project().setKey("NEW")) } }
  • 194. Создаем Issue в проекте public class TestCaseExportAction extends AnAction { public void writeToJira(Map<PsiMethod, TestCase> map) { map.forEach((method, testCase) -> { JiraIssue new = convert(testCase); JiraIssue created = jiraClient.create(new); String key = created.getKey(); addTmsLink(method, key); }); } }
  • 195. Проставляем @TmsLink public class TestCaseExportAction extends AnAction { public void writeToJira(Map<PsiMethod, TestCase> map) { map.forEach((method, testCase) -> { JiraIssue new = convert(testCase); JiraIssue created = jiraClient.create(new); String key = created.getKey(); addTmsLink(method, key); }); } }
  • 196.
  • 197.
  • 198. Какие еще есть идеи? Экспорт в другие системы Ревью автотестов
  • 199. Импорт в проект Экспорт из проекта Рефакторинг проекта Плагин за 10 минут