Zasada segregacji interfejsów na przykładzie Page Objectu
Spis treści
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.
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:
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.