JUnit 5 i Selenium - Wprowadzenie do wzrorca Page Object

W artukule JUnit 5 i Selenium - Odpowiedzialność metod testowych wprowadziłem zmiany, które poprawiły podział odpowiedzialności metod testowych w projekcie testów automatycznych dla aplikacji TodoMVC. Kolejnym ulepszeniem będzie wydzielenie odpowiednich metod prywatnych do oddzielnych klas i wprowadzenie wzorca Page Object.

O serii

Czytasz część 3 serii artykułów na temat automatyzacji testów aplikacji internetowej TodoMVC. Pozostałe części:

Kod źródłowy

Kod źródłowy opracowany w poprzednim artykule znajduje się w repozytorium Git: https://gitlab.com/qalabs/blog/junit5-selenium-todomvc-example w branchu 2-tests-responsibility. Klasa testowa, która zostanie zmodyfikowana w tym artykule, to TodoMvcTests, do jej kodu można przejść bezpośrednio po kliknięciu w link pl.qalabs.blog.junit5.selenium.todomvc.TodoMvcTests.

Rozdzielenie warstw

Po ostatnich zmianach w kodzie projektu możemy wyszczególnić trzy warstwy odpowiedzialności:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// warstwa 1 - logika testu

@Test
@DisplayName("Creates Todo with given name")
void createsTodo() {}

// warstwa 2 - manipulacja interfejsem użytkownika

private void createTodo(String todoName) {}

// warstwa 3 - opakowanie dostępu do `WebDriver API`

private WebElement find(By by) {}
private WebElement find(By by, SearchContext searchContext) {}
private void type(By by, CharSequence charSequence) {}
private void type(WebElement element, CharSequence value) {}

W kolejnych krokach usprawnię kod w taki sposób, aby poszczególne warstwy znalazły się w oddzielnych klasach i pakietach.

Logika Aplikacji - Page Object Pattern

Logika aplikacji (zawarta dotychczas w ogólnej klasie TodoMvcTests, z grubsza pomiędzy liniami 130 i 210 zostanie wyniesiona do klasy odpowiedzialnej tylko i wyłącznie za dostarczenie API związanego z obsługą strony TodoMvc. W naszym przykładzie API to jest reprezentowane przez pojedynczą klasę TodoMvc, która zawiera publiczne metody manipulacji interfejsem użytkownika:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TodoMvc {

public TodoMvc(Browser browser) {}
public void navigateTo() {}
public void createTodo(String todoName) {}
public void createTodos(String... todoNames) {}
public int getTodosLeft() {}
public boolean todoExists(String todoName) {}
public int getTodoCount() {}
public List<String> getTodos() {}
public void renameTodo(String todoName, String newTodoName) {}
public void removeTodo(String todoName) {}
public void completeTodo(String todoName) {}
public void completeAllTodos() {}
public void showActive() {}
public void showCompleted() {}
public void clearCompleted() {}
}

Ważną cechą nowej klasy jest to, że nie udostępnia swoim klientom (w naszym przypadku klasie testowej) żadnych informacji dotyczących obsługi WebDriver API. Ta obsługa jest realizowana przez obiekt klasy Browser, przekazany za pomocą konstruktora (o samej klasie Browser przeczytasz w dalszej części artykułu).

Jeżeli przyjrzymy się dokładniej metodom klasy TodoMvc to zauważymy, że udostępnia ona metody odzwierciedlające funkcje testowanej strony (page), w tym funkcję nawigacji do strony (metoda navigateTo()). Zatem klasa TodoMvc jest obiektem typu Page Object, gdyż reprezentuje funkcjonalność konkretnej strony, ukrywając jednocześnie szczegóły komunikacji z przeglądarką.

Używanie Page Objects w naszym teście jest bardzo proste: wystarczy zainicjalizować obiekt strony i wywoływać kolejne metody, weryfikując jednocześnie, czy zachowanie strony jest zgodne z oczekiwanym:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@BeforeEach
void beforeEach() {
// inicjalizacja sterownika oraz przygotowanie obiektów
todoMvc = ...;
// nawigacja do testowanej strony
todoMvc.navigateTo();
}

@Test
@DisplayName("Toggles selected Todo as completed")
void togglesTodoCompleted() {
String todoName = "One";
todoMvc.createTodos(todoName, "Two");

todoMvc.completeTodo(todoName);
assertEquals(1, todoMvc.getTodosLeft());

todoMvc.showCompleted();
assertEquals(1, todoMvc.getTodoCount());

todoMvc.showActive();
assertEquals(1, todoMvc.getTodoCount());
}

Browser Object

Obiekt TodoMvc potrzebuje dostępu do przeglądarki w celu zautomatyzowania poszczególnych funkcji aplikacji. Dostęp ten jest zrealizowany przez obiekty WebDriver API, a klasę, która opakowuje ten dostęp, nazwałem Browser. Klasa Browser dostarcza metody zawarte dotychczas w ogólnej klasie TodoMvcTests (pomiędzy liniami 212 i 251:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Browser {

public static Browser newBrowser() {}
public void open(String url) {}
public void quit() {}
public WebElement find(By by) {}
public WebElement find(By by, SearchContext searchContext) {}
public List<WebElement> findAll(By by) {}
public void type(By by, CharSequence charSequence) {}
public void type(WebElement element, CharSequence value) {}
public void click(By by) {}
public void click(By by, SearchContext searchContext) {}
public void moveToElement(WebElement element) {}
public void doubleClick(WebElement element) {}
public void executeScript(String script, Object... arguments) {}
}

Klasa Browser zawiera metodę statyczną newBrowser(), która pozwoli na inicjalizację sterownika przeglądarki. Pozostałe metody klasy Browser upraszczają korzystanie z WebDriver API w naszym projekcie. Obiekt klasy Browser inicjalizowany jest w metodzie @BeforeEach i następnie przekazywany do obiektu TodoMvc, który wykorzysta go do automatyzacji kolejnych funkcji aplikacji.

Tip: Jeżeli w trakcie implementowania Page Object okazałoby się, że potrzebuje bezpośredniego dostępu do WebDriver API, moglibyśmy stworzyć dodatkową metodę np. public RemoteWebDriver getWebDriver() {} i przy jej pomocy wyeksponować użyty sterownik przeglądarki.

Warto zwrócić uwagę na inną nową metodę, browser.quit(), której nie było we wcześniejszej wersji kodu. Metoda ta odpowiada za zamknięcie sterownika i wykorzystana została w teście:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@DisplayName("Managing Todos")
class TodoMvcTests {

private Browser browser;
private TodoMvc todoMvc;

@BeforeEach
void beforeEach() {
browser = Browser.newBrowser();
todoMvc = new TodoMvc(browser);
todoMvc.navigateTo();
}

@AfterEach
void afterEach() {
browser.quit();
}
}

Struktura pakietów

Po zmianach wprowadzonych powyżej uzyskaliśmy trzy klasy: TodoMvcTests, TodoMvc oraz Browser. Klasy te znajdują się aktualnie w tym samym pakiecie. Warto jednak pomyśleć o wydzieleniu klas do oddzielnych pakietów, tak aby zwiększyć czytelność kodu i ułatwić jego rozbudowę w przyszłości. Pakiety w Javie służą do grupowania podobnych typów klas czy interfejsów, zatem naturalnym będzie utworzenie pakietów dla poszczególnych rodzajów odpowiedzialności:

  • pl...todomvc.tests - klasy testowe (TodoMvcTests)
  • pl...todomvc.pages - klasy Page Objects (TodoMvc)
  • pl...todomvc.support - klasy wsparcia (Browser)

Diagram klas

Ostateczny diagram klas wygląda następująco:

Warto zauważyć, że zależności między klasami bięgną w jednym kierunku. Fakt, że klasa testowa zależy od klasy Browser, wynika wyłącznie z potrzeby inicjalizacji przeglądarki przed wykonaniem testów oraz zamknięciem jej po wykonaniu testów.

Podsumowanie

Używanie technik programowania obiektowego zwiększa jakość kodu oraz wpływa pozytywnie na utrzymanie i modyfikacje kodu w przyszłości. Sam wzorzeć Page Object nie jest wzorcem skomplikowanym. Wymaga on jednak zrozumienia podstawowych technik projektowania obiektowego, w celu modelowania bardziej skomplikowanych stron i interakcji.

Główne korzyści wynikające z używania tego wzorca to:

  • rozdzielenie odpowiedzialności między skryptami testowymi a fukcjonalnością aplikacji,
  • możliwość wielokrotnego użycia obiektów,
  • łatwość wprowadzania zmian, gdy funkcjonalność aplikacji się zmienia
  • zwiększona czytelność kodu

Dalsze kroki

Biblioteka Selenium dostarcza własną implementecję wzorca Page Object. Przeczytasz o niej w części 5 tej serii: JUnit 5 i Selenium - klasa PageFactory z pakietu support biblioteki Selenium

Repozytorium Git projektu

Kod źródłowy opracowany w artykule znajduje się w repozytorium Git: https://gitlab.com/qalabs/blog/junit5-selenium-todomvc-example w branchu 3-page-objects.

Rafał Borowiec

Nazywam się Rafał Borowiec. Jestem w branży IT od ponad 10 lat, przygodę rozpoczynałem jako tester oprogramowania. Oprócz testowania oprogramowania i zapewniania jakości, specjalizuję się w wytwarzaniu oprogramowania oraz zarządzaniu projektami i zespołami. Chętnie dzielę się wiedzą, prowadzę blog dotyczący programowania, jestem wykładowcą oraz trenerem IT.