[Code Recipe] Использование java reflection для мониторинга вызовов методов тестового класса с maven, testng и javassist

В поддержку новой инициативы - мега склад примеров на github
Создаем примеры по автоматизации вместе, просто присылайте pull requrest
А также инициативы создания at.info code recipes

Спасибо @ArtOfLife за отличный пример использования java reflection для мониторинга\перехвата вызовов методов тестового класса. Данный подход может быть использован, например для целей логгирования или изменения поведения методов в runtime или под какие-то другие ваши цели. В общем, смотрим код, разбираемся, и задаем вопросы @ArtOfLife

Конечный код

Реализация действий и страницы

Абстрактные классы для создания страницы и тестов

Аннотации

Реализация reflection для interceptor

Все файлы и проект можно найти на нашем мега-складе at.info-knowledge-base/functional test automation/webdriver/methods-interceptor-on-java at master · atinfo/at.info-knowledge-base · GitHub

Пусть таких примеров станет больше, присылайте pull request на наш мега-склад примеров.

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

Существует несколько способов реализации сей задачи, но все они, так или иначе, ведут к использованию прокси.

В данном примере используется библиотека javassist для перехвата / переопределения методов, на классы которых повешен прокси. Из кастомизации добавлен фильтр для перехвата только тех методов, которые аннотированы спец аннотацией Publish. Эксепшены фильтруются по наличию аннотации ThrowsException, ведь зачастую не от всех методов мы хотим получать исключительные ситуации. Не смотря на статику, имена методов с переданными параметрами хранятся в thread-safe контейнере, что исключает пересечение при масштабировании.

Пришлось навесить ряд абстракции, дабы смоделировать среду, очень близкую к реальной работе с WebDriver / PageObjects.

Буду рад видеть pull requests с оптимизацией / кастомизацией данного механизма, т.к. сам впервые начал использовать данную библиотеку.

2 лайка

Боян, для таких целей шикарно подходит AOP.

1 лайк

А что такое AOP в вашем понимании? И чем же приведенная реализация противоречит концепции AOP?
Наверное опять сбаяню, но тот же Spring AOP базируется на прокси.

Или вы все же глаголили об использовании готового AOP-based фреймворка? Так может поделитесь небаянистой имплементацией универсального перехватчика применительно к данной задаче?! Народ здесь простой, спасибо говорить умеем за хорошие примеры кода. Милости просим.

1 лайк

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

2 лайка

Чтобы утверждать про в разы лучше, надо бы для начала реализовать такое и подсчитать

1 лайк

Что “подсчитать”?
То что тут предложено, скорее всего работать будет. Но это не лучшее решение.
Во-первых это решение непосредственно вторгается в код. Это не всегда хорошо и не всегда возможно. Т.е это подойдет только для тех кто начинает с нуля делать тестовый проект и уже закладывает это решение. Которое опять же не самое лучшее и в будущем при расширении, например, может вызвать головную боль. Я хотел бы посмотреть как этот “Code Recipe” поможет какому то новичку который работает с уже проектом который делал не он.

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

Зачем флудить в теме с тэгом “База знаний” и лейблой “Code Recipe”?
Если я создам тему “Пузырьковая сортировка на java”, вы будете писать что это “шляпа” и “боян” и тыкать меня в сортировку с помощью двоичного дерева, не приводя конкретных примеров, и просто говорить, что это круто-удобно-быстро-инновационно?

1 лайк
public aspect TestExceptionInterceptor {
    pointcut testMethod(): execution(@Test * *(..));

    after() throwing (AssertionError e): testMethod() {
        //тут может быть что угодно
        System.out.println(thisJoinPointStaticPart.getSignature());
    }
}

Это как простейший пример Перехвата всех ассертов в тестовых классах. Если надо что то сложнее Это можно так же легко и непринужденно реализовать с помощью аспектов

Я понимаю чувства людей которые хотя собрать тут базу знаний, но ее надо собирать из “нормальных” знаний, а не всего что пришлют

1 лайк

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

  1. Все еще не вижу кода по перехвату вызова методов пейдж обджектов на Spring AOP или AspectJ.
  2. Вы советуете AspectJ, потом критикуете предложенный код за то, что он куда-то вторгается. Мне одному кажется это противоречивым? А то, что аспекты компилируются и вшиваются в основные классы, наверное для вас - новость?
  3. Вы советуете Spring AOP, рассказывая о неочевидности предложенного кода, который базируется на использовании прокси. А то, что что сам спинг аоп базируется на прокси, для вас тоже - новость?
  4. Сколько библиотек вам понадобится для вшития AspectJ аспектов?
  5. Что требует Spring AOP для организации перехвата, помимо всего прочего? Правильно, описание бинов в XML и их последующую загрузку.

Для размышлений: приведенный код использует 1 метод ReflectionUtils.create для создания прокси любого объекта. Page enum, вместо вызова конструктора пейдж обджекта, дергает create для наложения прокси на нужный нам класс (аналогичное действие по примеру загрузки бина при помощи спринга). О каких “размазываниях” по всему приложению речь? Вам чем то не угодила аннотация Publish? Хотите, спициально для вас назову ее, к примеру, Around для полной схожести с приведенными фреймворками. Смущает страшное название ReflectionUtils для навешивания прокси? Могу зашить ее в отдельную либу, не вопрос - чтобы вообще не было отличий от приведенных фреймворков.

Вывод тут только один: мой код использует 1 библиотеку javassist и 1 метод create для формирования прокси любому объекту. Новичку лишь понадобится вместо new SamplePage(driver) написать ReflectionUtils.create(SamplePage.class, driver). Сложно? Размыто? Сомневаюсь. Сколько телодвижений понадобится вам для решения той же задачи при помощи готового фреймворка?

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

  1. Вы советуете AspectJ, потом критикуете предложенный код за то, что он куда-то вторгается. Мне одному кажется это противоречивым? А то, что аспекты компилируются и вшиваются в основные классы, наверное для вас - новость?

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

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

Spring AOP это как один из примеров. При этом Если тесты используют TESTNG то самый базовый тестовый класс просто можно отнаследовать от AbstractTestNGSpringContextTests. Дальнейшая настройка конфигурации в xml займет не больше 15 минут. При этом вы не лезете в создание страниц и тд

Касательно AspectJ. programming addicted: Weaving with AspectJ добавляете в ваш сборщик проекта указания для аспектов и все. В код вы не лезете.

И еще AspectJ и Spring AOP это немного разные вещи. Да, Spring работает на проксях, Как и AspectJ при желании. Но AspectJ так же умеет работать в рантайме. Что очень удобно, когда нельзя трогать код.

З,Ы. Я понимаю вы защищаете то что сделали. Но от этого оно лучше или полезней не станет

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

Разница в том, что я защищаю конкретную реализацию в один класс, что позволит людям, как минимум, получить знания о том, как устроена работа с прокси. Как максимум - применить механизм для своих целей. И уж поверьте, легче будет разобраться в 1 классе с несколькими методами, нежели - в целом фреймворке, который еще придется конфигурировать особым образом. База знаний на то и придумана, чтобы делиться различными исходниками / подходами. А какой из них применять - личное дело каждого.

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

1 лайк

Использовать Spring, если он подключается только для вот такой мелкой задачи - это как стрелять из пушки по воробьям.

Зачем критиковать какое-то решение, которое работает и в определенных условиях имеет свои преимущества по сравнению с тем, что ты предлагаешь? Ты говоришь про условия “когда нельзя вторгаться в код”. Здесь же предложено решение для других условий.

Тот кто будет читать Базу знаний, будет выбирать решение под себя. И поэтому разные решения похожих задач очень полезны.

Сделай доброе дело, добавь подобную статью с реализацией на AOP-фреймворке подобной (не обязательно этой же, можно какой-то своей) задачи. От этого всем будет только польза

2 лайка

Делиться я с вами не буду) NDA компании не позволит.
А по поводу перехвата:
-ваши желания начнут расти, вам захочется не только получать статистику, но и менять в рантайме реализацию сервисов (API ->GUI, GUI->API, потому что вам не захочется из-за одного бага в GUI стопнуть все тестирование, но это когда дорастете до большого проекта).
-Вам может захотеться логировать выполнение из javadoc аннотаций, плюс получать с какими параметрами выполнялась функция и с какими значениями получился return.
-плюс использование чужого кода (который поддерживается сообществом) уменьшает затраты на поддержку тестов.
-то что вы написали это прекрасно с одной стороны, все же опыт и плюс вам в карму, но с другой я бы не стал советовать такой код новичкам, которые сидят на legacy проектах. (Есть баги в коде, я про архитектуру в целом, но это совсем другая история)
Удачи вам…

Это каким же образом от одного бага в GUI может стопнуться все тестирование? Или вы кидаете рантайм эксепшены везде, где только можно? В случае, если в приложении действительно присутствует блокер, пардон, но с какой радости я должен его обходить окольными путями? Из теории тестирования вам наверняка известно, что если смоук ран билда завалился из-за блокеров, то дальнейшее тестирование - нецелесообразно и билд отправляется на доработку. А от того, что вы искусственно заставите ваши тесты получать статус passed в обход явных багов, ваше приложение качественней не станет.

Мы сейчас точно говорим о функциональном UI тестировании? Вообще говоря, в сети ходит немало холиваров насчет целесообразности подключения логеров для автоматизации тестирования ПО. На практике: зачем мне лезть в красивенькие логи, если стектрейс четко укажет на проблему? Кто-то, кроме вас и других автоматизаторов, вообще будет заглядывать в логи не продакшен кода? Вы уверены, что логи укажут вам на проблему быстрее, чем стектрейс? Логировать входные / выходные параметры функции можно при помощи Log4J 2. Инжектить аспекты ради логирования функциональных UI тестов - нонсенс. Если речь идет о другом тестировании, то пардон, но код не зря залит в раздел webdriver.

А причем тут NDA? Вы являетесь разработчиком AspectJ или Spring AOP? В противном случае, абстрактный пример кода с использованием указанных фреймворков не нарушит ни чьи права.

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

1 лайк

Ты думаешь тут все код выкладывают исключительно в нарушение NDA?

Напиши с нуля. На это же уйдёт 15 минут по твоему утверждению. Ты на комменты больше времени потратил.

P.S. Ради справедливости добавлю, что про 15 минут утверждал @alexkuzpro :slight_smile: Который впрочем тоже пока не показал рабочего кода, к сожалению

1 лайк

Развели тут какой-то флейм…
И я подброшу дровишек!

Что у нас в сухом остатке:
Товарищи @alexkuzpro и @Patzifist наверное потратили огромное количество времени на непонятные доводы по поводу «лучшей» реализации, но так и не смогли показать код «лучшего» решения.

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

У себя я использовал AOP для логирования шагов. Вот статья по теме:
C#: Как добавить автоматическое логирование вызова метода при помощи Castle DynamicProxy и Humanizer. Там есть ссылки на рабочий gist реализации для .Net

Из плюсов:

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

  • Можно залогировать информацию о времени прохождения конкретного шага (метода) и тем самым узнать где проседает время выполнения

  • Каждый шаг можно декорировать собственными атрибутами

Из минусов:

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

  • Сам прокси для объекта требовал, чтобы я объявлял методы, которые необходимо перехватывать с модификатором «virtual» иначе – код будет работать, но методы без virtual просто не будут перехватываться.

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

Альтернативы:
В свое время, мне нравился подход фреймворка BDDfy с его Fluent-стилем.
http://docs.teststack.net/bddfy/index.html

В C# это выглядело более или менее красиво. Но в Java 7… был бы синтаксический ужос, ведь в C# использовались лямбды и замыкания.

        [TestMethod]
        public void CardHasBeenDisabled()
        {
            this.Given(s => s.GivenTheCardIsDisabled())
                .When(s => s.WhenTheAccountHolderRequests(20))
                .Then(s => s.CardIsRetained(true), "Then the ATM should retain the card")
                    .And(s => s.AndTheAtmShouldSayTheCardHasBeenRetained())
                .BDDfy();
        }

В Java 8 уже есть лямбды. Так что – удачи :smiley:

1 лайк