Архитектура тестов при модульной структуре тестируемого приложения

design-patterns
architecture
framework
page-object
Теги: #<Tag:0x00007fedb7b52410> #<Tag:0x00007fedb7b52208> #<Tag:0x00007fedb7b520c8> #<Tag:0x00007fedb7b51f60>

(Анна) #1

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

Как это выглядит на практике:
Например, есть форма логин дефолтная. И есть модуль 1 - которыый добавляет к этой форме пару новых кнопок и модуль 2, который добавляет чекбокс.

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

Есть дефолтный блок логин формы. Есть блок логин формы+модуль 1, логин формы+модуль 2.

Предположим у меня на прокте установлен модуль 1: тут все просто, я в логин пейдже этого проекта буду использовать блок логин форма+модуль 1. То же самое есть используется только модуль 2.
А вот как быть если используются оба модуля? Включать в пейджу оба блока - которые по сути одинаковые кроме пары дополнительных кнопок? Делать отдельный класс логин форма + модуль 1 + модуль 2 - но таких модулей может быть, скажем, не 2, а 5 - и количество возможных комбинаций будет расти?

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

Upd:

Core:

LoginForm extends BasePage {

private WebElement username;
private WebElement password;

//Геттеры, сеттеры для полей
}
Loginage extends BasePage{
private LoginForm loginForm;

//Другие блоки, геттеры для дотупа к ним
}

Module 1:

LoginFormModule1 extends LoginForm{
private WebElement additionalButton1;
}

Module 2:

LoginFormModule2 extends LoginForm{
private WebElement addtionalButton2;
}

Project 1 uses only core+module 1:

LoginPageProject1 extends LoginPage{
//вместо дефолтной логин формы подставляем форму расширенную модулем 1
private LoginFormModule1 loginForm;
}

Project 2 uses core + module 1 + module 2

LoginPageProject2 extends LoginPage{
//И вот здесь начинается проблема

//Вариант 1 - добавить оба блока логин формы - с расширением от модулей 1 и 2
private LoginformModule1 loginFormModule1;
private LoginFormModule2 loginFormModule2;

//Вариант 2 - создать блок LoginFormModule1Modiule2 и включить его в класс пейджы
private LoginFormModule1Module2 loginForm;

}


(Михаил Братухин) #2

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

И вот тут еще выступление есть от Сергея на недавнем Selenium Camp’е:
How does Java 8 exert hidden power on Test Automation? (Sergey Korol, Ukraine) (интересно почему его в конце спрашивали про различия интерфейсов и абстрактных классов и не связано ли это с той самом статьей, что я привел выше… :sweat_smile:)


(Sergey Korol) #3

Мы можем это выяснить у автора вопроса. Да, @Sergey_Pirogov? :smiley:


(Oleksii Ihnatiuk) #4

А ваш вопрос разве не решает композиция? Недавно была похожая тема и Сергей посоветовал почитать об > Inheritance / Composition over inheritance . Думаю для вашего случая это также актуально.


(Sergey Pirogov) #5

Стандартный вопрос на собеседовании :wink: Задавал не ради провокации, а ради того чтобы ответ остался в видео и люди могли узнать разницу =)


(Анна) #6

Спасибо за ссылки, очень познавательно, но боюсь мне не поможет. Мне как раз нужно наследовать не поведение, а состояние - т.е. в данном случае элементы формы


(Анна) #7

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


(Михаил Братухин) #8

Хм… не уверен, что вам подойдет. Точнее насколько оно будет эстетично смотреться в вашем коде, но можно посмотреть в сторону шаблона Декоратор и Строитель.
У Микалая на Selenuim Camp’е был беглый обзор различных паттернов проектирования:
Design patterns in test automation (Mikalai Alimenkou, Ukraine)

И вот тут немного про Декоратор есть:
Шаблон Decorator(Декоратор) на Java

Но опять-таки нужно смотреть по ситуации.


(Sergey Korol) #9

Давайте начнем с простого: приведите примеры того, как выглядит core, module, page, block?

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

Вместо тысячи слов гораздо проще для восприятия будет предоставить реальные code samples.


(Анна) #10

Да, вы правы :slight_smile: отредактировала тему с добавлением примеров кода. Надеюсь так моя проблема будет понятнее.


(Sergey Korol) #11

Осталось только код отформатировать, чтобы наверняка стало понятно. :wink:

P.S. Хотя, вижу, что пока писал сообщение, уже сами разобрались. :slight_smile:


(Taras) #12

@Anna_Ostrovskaya я думаю Вам HTML Elements может заканать для Вашей проблеми


(Alexei Vinogradov) #13

Ой-йо-ёй, как много ненужного кода, модулей, фреймфорков.

через пару дней будет на http://testconf.ru доклад KISS PageObjects про то, как всё это делать быстрее, эффективнее и стабильней. Надеюсь рано или поздно появится в открытом доступе.


(Александр Илюшкин) #14

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


(Oleksii Ihnatiuk) #15

Как сейчас выглядит Ваша архитектура:

Как я считаю она должна выглядеть:

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

  2. У вас будет один класс Login Page с элементами, которые есть везде;

  3. У вас будет n-ое количество дополнительных классов с некими специфическими элементами (LoginFormModule1, LoginFormModule2 …), которые будут наследовать Base Page класс и будут добавлены через композицию (не через наследование!) в класс Login Page с геттерами;

  4. Соединять все эти связи в некий LoginPageProject1 класс я считаю крайне избыточным, так как все нужные нам элементы и публичные методы уже готовы;

  5. В тестовом классе вы будете соединять разные блоки как вам захочется например:

public FirstProjectTC class extended BaseTest {

@Test
public void logIn() {
   homePage
           .openLogInForm()
           .typePassword("123456")
           .getLoginFormModule1()
           .typeInSpesialField();
}
}

Надеюсь что помог Вам.


(Vatslau) #16

А почему не использовать просто итрефейс или перегруженный метод передавая параметрами конфиг?

public boolean checkLogin(Boolean core, Bolean form, Boolean dataIsValid)
if(core==true){
do this
}
if(form==true){
do this
}
If(dataIsValid==true){
Assert
return true}
else{return false}


(Oleksii Ihnatiuk) #17

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


(Анна) #18

Это не так. Модули разные и независимые, могут быть включены все, могут через один :slight_smile:


(Vatslau) #19

я сделал примерно так
selectReceiveEmailOptions(
true,
false,
false,
true,
true
);
5 опций(модулей) - если тру производим действие, фолс - игнорируем
расширяемость - до бесконечности ide - подсказывает плейсходерали какой параметр чем управляет
public static void selectReceiveEmailOptions(Boolean receiveAlways, Boolean receiveOffline, Boolean noEmails, Boolean getAttachment, Boolean getCopyOwnMsg){
if(receiveAlways==true) {
rootLogger.info(“Select receive get emails Always radio”);
EMAILS_TAB_RADIO_ALWAYS.waitUntil(visible, 20000).setSelected(true);
EMAILS_TAB_RADIO_ALWAYS.shouldBe(selected);
}
if(receiveOffline==true) {
rootLogger.info(“Select receive get emails if user is offline radio”);
EMAILS_TAB_RADIO_OFFLINE.waitUntil(visible, 20000).setSelected(true);
EMAILS_TAB_RADIO_OFFLINE.shouldBe(selected);
}
if(noEmails==true) {
rootLogger.info(“Select no Always radio”);
EMAILS_TAB_RADIO_NO_EMAILS.waitUntil(visible, 20000).setSelected(true);
EMAILS_TAB_RADIO_NO_EMAILS.shouldBe(selected);
EMAILS_TAB_RECEIVE_ATTACHMENTS.shouldBe(disabled);
}
if(getAttachment==true && noEmails==false) {
rootLogger.info(“Set get Attachment checkbox”);
EMAILS_TAB_RECEIVE_ATTACHMENTS.waitUntil(visible, 20000).setSelected(true);
EMAILS_TAB_RECEIVE_ATTACHMENTS.shouldBe(checked);
}
if(getAttachment==false && noEmails==false) {
rootLogger.info(“Set get Attachment checkbox”);
EMAILS_TAB_RECEIVE_ATTACHMENTS.waitUntil(visible, 20000).setSelected(false);
EMAILS_TAB_RECEIVE_ATTACHMENTS.shouldNotBe(checked);
}
if(getCopyOwnMsg==true) {
rootLogger.info(“Set get copy own messages as emails checkbox”);
EMAILS_TAB_GET_COPY_OWN_EMAILS.waitUntil(visible, 20000).setSelected(true);
EMAILS_TAB_GET_COPY_OWN_EMAILS.shouldBe(checked);
}
if(getCopyOwnMsg==false) {
rootLogger.info(“Set get copy own messages as emails checkbox”);
EMAILS_TAB_GET_COPY_OWN_EMAILS.waitUntil(visible, 20000).setSelected(false);
EMAILS_TAB_GET_COPY_OWN_EMAILS.shouldNotBe(checked);
}
}


(Eugene Moskalenko) #20

Оу… а я как раз жду этот самый доклад, тоже надеюсь… Хотелось бы глянуть…