O testach UI, szczególnie w SpecFlow pisałem już kilka razy. Zawsze korzystałem z wzorca PageObject, chociaż nie wiedziałem, że ma on swoją nazwę. Czasami mam wrażenie, że na proste rzeczy wymyśla się “wzorce”. Muszę przyznać jednak, że uproszcza to często komunikację między programistami. Wzorce w końcu stanowią pewnego rodzaju słownictwo dla programistów. Zamiast opisywać coś w kilku zdaniach, można powiedzieć po prostu nazwę wzorca.
PageObject pattern polega na tym, że pisząc testy jednostkowe, opakowujemy stronę w obiekty C#. Początkujący programiści często piszą testy w stylu:
[Test] public void Query_ShouldBe_Persisted_When_SearchButtonIsClicked() { IWebDriver driver = new ChromeDriver(); driver.Navigate().GoToUrl("http://www.bing.com"); var textBox = driver.FindElement(By.Id("sb_form_q")); textBox.SendKeys("piotr zielinski c#"); var searchButton = driver.FindElement(By.Id("sb_form_go")); searchButton.Click(); string query = driver.FindElement(By.Id("sb_form_q")).GetAttribute("value"); Assert.That(query,Is.EqualTo("piotr zielinski c#")); }
Test jest trudny w zrozumieniu oraz czasochłonny w utrzymaniu. Idealny test powinien być czytany jak skrypt opisujący kolejne akcje. W PageObject opakowujemy strony w obiekty c#. W naszym przypadku stworzymy dwa obiekty, SearchForm oraz SearchResults:
public class SearchForm { private readonly IWebDriver _webDriver; public SearchForm(IWebDriver webDriver) { _webDriver = webDriver; } public SearchResults Search(string query) { var textBox = _webDriver.FindElement(By.Id("sb_form_q")); textBox.SendKeys(query); var searchButton = _webDriver.FindElement(By.Id("sb_form_go")); searchButton.Click(); return new SearchResults(_webDriver); } } public class SearchResults { private readonly IWebDriver _webDriver; public SearchResults(IWebDriver webDriver) { this._webDriver = webDriver; } public string GetQuery() { return _webDriver.FindElement(By.Id("sb_form_q")).GetAttribute("value"); } }
Test zatem wygląda teraz następująco:
IWebDriver driver = new ChromeDriver(); driver.Navigate().GoToUrl("http://www.bing.com"); const string query = "Piotr zielinski c#"; var searchForm=new SearchForm(driver); var searchResults = searchForm.Search(query); Assert.That(searchResults.GetQuery(), Is.EqualTo(query));
Zwykle opakowuję również sterownik czyli interfejs IWebDriver. Można stworzyć obiekt o nazwie np. BingWebsite który będzie stanowił bramkę do wszelkich testów. Celem jest, aby jakikolwiek kod Selenium nie znajdował się bezpośrednio w teście. Dzięki temu, ten sam kod może być wykorzystywany w różnych testach. Ponadto, przy skomplikowanych testach, kod jest dużo prostszy w rozumieniu ponieważ stanowi skrypt bez magicznych zmiennych.
Czy dobrym pomysłem jest użycie wzorca PageObject w kodzie produkcyjnym? Pytam, bo słyszałem, że wzorzec PageObject to to antywzorzec.
Dlaczego anty-wzorzec?
Anty-wzorzec ponieważ strona często się zmienia, przez co klasa ją reprezentująca ma zbyt dużo powodów do zmian.
@ciekawski
Jak strona sie czesto zmienia, to testy prawdopodobnie tez bedzie trzeba czesto zmieniac niezaleznie czy zaimplementowano wzorzec czy nie. Im wiecej testow w projekcie, tym wiecej kodu do utrzymania.