Zasada segregacji interfejsów na przykładzie Page Objectu

Segregacja interfejsów

Zasada segregacji interfejsów (I -- interface segregation) jest jedną z najprostszych zasad ze zbioru SOLID. W oryginale brzmi ona:

"Wiele dedykowanych interfejsów jest lepsze niż jeden ogólny".

Z powyższego zdania wynika, że lepiej jest mieć klasę która implementuje wiele interfejsów, niż klasę, która implementuje jeden duży interfejs z wieloma metodami. Ale przejdźmy do przykładu.

Scenariusz

Załóżmy, że automatyzujemy aplikację, w której mamy bardzo dużo modalnych okiem z ang. modal window.

Większość okien w aplikacji wygląda tak na obrazku poniżej. Oczywiście jest to okno przykładowe.

przykladowy popup

Przykładowe modalne okno składa się z:

  • Tytułu okna,
  • Treści w oknie,
  • Przycisku zatwierdzenia (confirm),
  • Przycisku negacji (cancel),

Załóżmy, że chcielibyśmy teraz wprowadzić zasadę segregacji interfejsów do Page Objectów, które implementują powyższy przykład.

Przykład niepoprawny

Ponieważ, w aplikacji którą automatyzujemy jest wiele taki okiem modal. To dla porządku każde okno modal będzie posiadało osobny Page Object. Niestety nie możemy stworzyć generycznego Page Objectu ponieważ, lokatory w oknach zawsze są różne. Poniżej przykładowy Page Object o nazwie ConfirmModalPage:

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import static org.testng.Assert.assertEquals;

public class ConfirmModalPage extends BasePage {

    @FindBy(name = "modal-title")
    private WebElement modalTitleLabel;

    @FindBy(name = "modal-question")
    private WebElement contentModalLabel;

    @FindBy(name = "modal-confirm-btn")
    private WebElement confirmButton;

    @FindBy(css = "cancel-button")
    private WebElement cancelButton;

    public void assertModalTitle(String modalTitle) {
        assertEquals(modalTitleLabel.getText(), modalTitle);
    }

    public void assertModalContent(String modalContent) {
        assertEquals(contentModalLabel.getText(), modalContent);
    }

    public void clickOnCancelButton() {
        cancelButton.click();
    }

    public void clickOnConfirmButton() {
        confirmButton.click();
    }

}

Ponieważ chcemy zachować spójność z innymi modalami w aplikacji, tworzymy interfejs o nazwie ModalWindow, w którym umieszczamy wszystkie wspólne metody dla wszystkich okien. I tak mamy:

public interface ModalWindow {
    
    void assertModalTitle(String modalTitle);
    void assertModalContent(String modalQuestion);
    void clickOnCancelButton();
    void clickOnConfirmButton();
    
}

Ostatecznie nasza przykładowa klasa ConfirmModalPage wygląda następująco:

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import static org.testng.Assert.assertEquals;

public class ConfirmModalPage extends BasePage implements ModalWindow {

    @FindBy(name = "modal-title")
    private WebElement modalTitleLabel;

    @FindBy(name = "modal-question")
    private WebElement contentModalLabel;

    @FindBy(name = "modal-confirm-btn")
    private WebElement confirmButton;

    @FindBy(css = "cancel-button")
    private WebElement cancelButton;

    public void assertModalTitle(String modalTitle) {
        assertEquals(modalTitleLabel.getText(), modalTitle);
    }

    public void assertModalContent(String modalContent) {
        assertEquals(contentModalLabel.getText(), modalContent);
    }

    public void clickOnCancelButton() {
        cancelButton.click();
    }

    public void clickOnConfirmButton() {
        confirmButton.click();
    }

}

Dzięki zastosowaniu interfejsu dbamy o to, aby wszystkie Page Objecty, które pomimo różnych wartości lokatorów dla WebElementów miały takie same nazwy metod. Tym samym były łatwe w utrzymaniu i spójne między sobą.

Problem pojawia się w momencie kiedy do aplikacji, którą automatyzujemy zostanie dodane okno modal, które wygląda inaczej. Na przykład, tak jak na obrazku poniżej:

popup newsletter

Podany powyżej przykład:

  • Posiada tytuł okna,
  • Posiada treść okna,
  • Posiada przycisk zatwierdzenia (confirm),
  • Nie posiada przycisku negacji,
  • Ma inne elementy takie jak pole do wpisywania, której dla uproszczenia pominiemy.

W z związku z czym nie możemy zastosować interfejsu ModalWindow ponieważ, nowy modal nie posiada takich samych cech. Jak w takim razie poradzić sobie z tą sytuacją?

Odpowiedzią na to pytanie jest oczywiście segregacja interfejsów w myśl:

"Wiele dedykowanych interfejsów jest lepsze niż jeden ogólny".

Poprawny przykład

Zaczynamy od rozbicia interfejsu ModalWindow na mniejsze, i tak mamy:

Interfejs ModalTitle:

public interface ModalTitle {

    void assertModalTitle(String modalTitle);

}

Interfejs ModalContent:

public interface ModalContent {

void assertModalContent(String modalQuestion);

}

Interfejs ModalConfirmButton:

public interface ModalConfirmButton {

    void clickOnConfirmButton();

}

Interfejs ModalCancelButton:

public interface ModalCancelButton {

    void clickOnCancelButton();

}

Następnie dla klasy ConfirmModalPagemodyfikujemy implementację:

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import static org.testng.Assert.assertEquals;

public class ConfirmModalPage extends BasePage implements ModalTitle, ModalContent, ModalConfirmButton, ModalCancelButton {

    @FindBy(name = "modal-title")
    private WebElement modalTitleLabel;

    @FindBy(name = "modal-question")
    private WebElement contentModalLabel;

    @FindBy(name = "modal-confirm-btn")
    private WebElement confirmButton;

    @FindBy(css = "cancel-button")
    private WebElement cancelButton;

    public void assertModalTitle(String modalTitle) {
        assertEquals(modalTitleLabel.getText(), modalTitle);
    }

    public void assertModalContent(String modalContent) {
        assertEquals(contentModalLabel.getText(), modalContent);
    }

    public void clickOnCancelButton() {
        cancelButton.click();
    }

    public void clickOnConfirmButton() {
        confirmButton.click();
    }

}

Dla nowego okna tworzymy klasę SubscribeModalWindow , która implementuje tylko interfejsy ModalTitle , ModalContent , ModalConfirmButton:

import example.solid.i.bad.BasePage;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import static org.testng.Assert.assertEquals;

public class SubscribeModalPage extends BasePage implements ModalTitle, ModalContent, ModalConfirmButton {

    @FindBy(name = "modal-title")
    private WebElement modalTitleLabel;

    @FindBy(name = "modal-question")
    private WebElement contentModalLabel;

    @FindBy(name = "modal-confirm-btn")
    private WebElement confirmButton;

    public void assertModalTitle(String modalTitle) {
        assertEquals(modalTitleLabel.getText(), modalTitle);
    }

    public void assertModalContent(String modalContent) {
        assertEquals(contentModalLabel.getText(), modalContent);
    }

    public void clickOnConfirmButton() {
        confirmButton.click();
    }

}

Dzięki rozbiciu interfejsu ModalWindow na mniejsze:

  • Uzyskaliśmy większą elastyczność kod,
  • Jesteśmy w stanie obsłużyć więcej specyficznych przypadków,
  • Spełniamy zasadę segregacji interfejsów.

To było na tyle!

Potrzebujesz więcej informacji?

Artykuł powstał na bazie kursu Automatyzacja testów z wykorzystaniem Selenium!

O Autorze

Nazywam się Mateusz Ciołek i od 2011 roku zajmuję się testowaniem oprogramowania ze specjalizacją w automatyzacji testów.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.