W serii artykułów przedstawimy Ci instrukcję, jak pisać testy automatyczne przy użyciu frameworka SpecFlow, Selenium WebDriver i C#.

 

Oto druga część serii wpisów o tworzeniu testów automatycznych przy użyciu Specflow, Selenium WebDriver i C#. Część pierwszą znajdziesz tutaj...

Czego dowiesz się z tego wpisu?

  • Napiszesz swój pierwszy test akceptacyjny w SpecFlow wraz z użyciem Selenium WebDriver oraz C#.
  • Nauczysz się debugować testy.

Jako strony docelowej do naszych testów automatycznych użyjemy przykładowego bloga, o którym wspominałem w poprzednim wpisie: https://courseofautomationtesting.wordpress.com/. Blog pozwoli Ci bez przeszkód uczyć się automatyzacji na bardziej realnym przykładzie.

Czym jest Selenium WebDriver?

Selenium WebDriver to framework, który pozwala nam tworzyć testy automatyczne interfejsu użytkownika. Każda z popularnych przeglądarek implementuje standard zgodny z Selenium WebDriver dzięki temu możemy ich używać.

Jak będzie wyglądać nasz scenariusz?

  • Wejście na bloga
  • Kliknięcie contact w menu
  • Wypełnienie formularza kontaktowego i wysłanie go
  • Sprawdzanie wyświetlania komunikatu o pozytywnym wysłaniu wiadomości

Zaczynamy.

Otwieramy projekt, który utworzyliśmy w pierwszej części wpisu (link) w Visual Studio 2017. Przechodzimy do dodania Feature.

Jak dodać plik feature do projektu?

1  Prawy przycisk myszy na folderze -” Features” 
2. New item

2.1..png

3. Naszym oczom ukaże się okno, w którym możemy dodać nasz feature.

2.2..png

Nasz feature domyślnie dodał się z przykładowym scenariuszem. (zdjęcie poniżej).  Zauważ, że poszczególne kroki scenariusza mają filetową czcionkę. Dzieje się tak, ponieważ SpecFlow nie widzi implementacji dla tych kroków. Po dodaniu step definition czcionka kroku przybierze kolor biały.

2.3..png

Teraz piszemy scenariusz, który chcemy zaimplementować.

Poniżej przykładowy scenariusz, który wklejamy do pliku feature.

Scenario: As User I enter to blog and I'm contacting by contact form. 
 Given I enter to "home” page  
 And I click on ”Contact" in menu 
 When I fill contact form 
 Then I expect to see message as „Message Sent (go back)”

Również te kroki mają fioletowy kolor czcionki, ponieważ nie mamy dodanej implementacji dla poszczególnych kroków.

Czym są kroki (steps)?

Krokiem nazywamy każdą akcję rozpoczynają się od słówka kluczowego Given, When, Then, And. W kodzie każdy z kroków definiowany jest jako metoda i rozpoznawany po wyrażeniu regularnym, które zapisane jest nad metodą.

2.4..png

SpecFlow nie może posiadać dwóch takich samych wyrażeń regularnych dla kroków, w związku z tym ważne jest tworzenie kroków o różnej składni. Oczywiście możemy tworzyć generyczne kroki i robić więcej niż jedną akcję np. poprzez parametr/parametry.

Jak dodać kroki do projektu (step definition)?

  1. Klikamy prawy przycisk myszy na folderze „Steps”
  2. Z okna dialogowego, która nam się ukaże wybieramy Step definition i nazwę CheckContactFormSteps.cs

Po dodaniu klasy dla step definition, przechodzimy do dodania implementacji dla poszczególnych kroków.

Jak to zrobić?

Klikamy prawy przycisk myszy na, jednym z "kroków". Wybieramy opcję w menu „Generate Step Definitions”

2.5..png

2.6.png

Klikamy „Copy methods to clipboard”. W naszym schowku mamy “szkielet” struktury kroków, który wklejamy do ContactFormSteps.cs (zamiast domyślnych metod, które są w tej klasie).

Jak działa przekazywanie parametrów w SpecFlow?

Podstawowym sposobem przekazywania parametrów w scenariuszach jest dodanie parametru jako zmienna. SpecFlow po wyrażeniu regularnym rozpoznaje jaka część składni kroku ma być brana jako parametr.

2.7..png

”” (.*)”” - w wyrażeniu regularnym oznacza to przekazywanie parametru z testu, który mamy w metodzie GivenIEnterToPage.

Teraz zmieniamy nazwy zmiennych, które są ustawione domyślnie jako parametr. W naszym przypadku jest to np. zmienna p0.

Po ustawieniu możemy przejść do implementacji kroków scenariusza.

Upewniamy się, że mamy wszystkie potrzebne NuGet packages.

2.8.png

Przed dodaniem implementacji do poszczególnych kroków musimy dodać klasę pomocniczą tzw. hook.

Hook jest to klasa, która pozwala używać specjalnych atrybutów w naszym kodzie m.in.:

  • [AfterStep]  Atrybut, którego używamy, w przypadku gdy chcemy, aby nasza zdefiniowana metoda uruchamiała się po każdym kroku. W tym miejscu możemy na przykład mieć akcję sprawdzającą, czy w danym kroku nie wystąpił błąd i zrobić screenshot, jeżeli błąd wystąpił.
  • [BeforeScenario] Atrybut, którego używamy, gdy chcemy żeby nasza zdefiniowana metoda uruchamiała się przed każdym scenariuszem testowym.
  • [AfterScenario] Atrybut, którego używamy, gdy chcemy żeby nasza zdefiniowana metoda uruchamiała się po scenariuszu. W tym miejscu możemy np. czyścić instancje obiektów, takich jak WebDriver czy ObjectContainer.
  • [BeforeStep] Atrybut pełniący podobną funkcję do [AfterStep] - tylko po danym kroku, nie przed.
  • [BeforeTestRun] Atrybut, którego możemy użyć przed całym TestRunem. TestRun jest to zbiór naszych testów, które uruchamiamy w danym momencie. Może to być zarówno jeden test, jak i 50 testów. Jest to atrybut, którego możemy użyć do tworzenia danych testowych.

Jak dodać Hook do naszych testów?

W naszym projekcie testów dodajemy folder, który nazwiemy Settings. Następnie klikamy prawy przycisk myszy i z menu wybieramy Add New Item. Klikamy.

2.9.png

Nasz Hook nazwamy jako TestsHook.cs. Następnie dodajemy poniższy kod:

2.10.png

Linjka [10] oznacza deklarację zmiennej IWebDriver.

IWebDriver to interfejs, który implementuje wszystkie Drivery i dzięki temu możemy w łatwy sposób podmienić inicjalizację tej zmiennej na drivera, który interesuje nas w danej chwili. Jeżeli nie wiesz czym są interfejsy w C# zachęcam do zapoznania się z tym tematem.

Następnie dodajemy metodę BeforeScenario() wraz z atrybutem [BeforeScenario]. SpecFlow wie, że wejście w tą metodę będzie pierwsze. Następnie przechodzimy do zainicjalizowania tej zmiennej wybranym przez nas Driverem.

Jednak nie dodaliśmy jeszcze klasy DriverFactory wraz z metodą ReturnDriver (DriverType driverType), która ma pomóc w tym zadaniu. Dla porządku dodajemy w naszym projekcie folder o nazwie „Helper” gdzie będziemy składowali wszystkie nasze pomocnicze klasy. Następnie dodajemy klasę FactoryDriver do tego folderu.

2.11..png

Po dodaniu klasy możemy dodać potrzebną dla nas metodę, która będzie zwracać nam zadanego drivera. Jako parametr utworzyłem enuma, który pozwoli nam decydować, przez dowolnego drivera.

2.12.png

Następnie utworzyłem switcha do tego. Jeżeli nie znasz tej sztuczki możesz utworzyć szybciej switcha z wartościami z enuma poprzez R# (zdjęcie poniżej).

2.13.png

Alt + Enter-powoduje pokazanie się podpowiedzi w zmianie kodu, która sugeruje nam R#

Po utworzeniu switcha, pozostaje nam dodanie zmiennej i prostej inicjalizacji dla każdego case’a ze switcha. Gdy już mamy dodaną pomocniczą klasę, z naszą metodą do tworzenia określonego drivera, wracamy do pliku TestsHooks.cs

3.14.png

Linijka [15] to dodanie do ScenarioContext naszego drivera.

ScenarioContext.Current -  to słownik, przechowujący wartości/zmienne, które możemy użyć w dowolnym miejscu naszych testów. Do każdej z wartości możemy się odwołać za pomocą klucza, który podawany jest jako string.

W dzisiejszym wpisie zrobimy to „brzydko” bez użycia dobrego wzorca takiego jak dependency injection (o tym napiszę w kolejnych wpisach). Teraz chcę żebyście mogli przejść z etapu „bardzo podstawowy / nie najlepiej napisany test” do etapu „nieźle napisany test”.

Po dodaniu naszego drivera do ScenarioContext.Current przechodzimy do napisania metody, która wykona się na końcu egzekucji naszego testu.

Ponownie dodajemy atrybut tym razem [AfterScenario] oraz metody:

  • driver.Close() - metoda, która zamyka naszego drivera.
  • driver.Dispose() - metoda, która opróżnia z pamięci obiekt drivera oraz zamyka okno konsoli, uruchamianej wraz z konkretną przeglądarką.

W tym miejscu przechodzimy do naszej klasy ContactFormSteps.cs, w której dodamy implementacje dla poszczególnych kroków. Zaczynamy od dodania zmiennej typu IWebDriver, którą zainicjalizujemy w konstruktorze (który również dodamy). Inicjalizacja będzie polegała na użyciu wartości z naszego pomocniczego słownika ScenarioContext.Current

2.15.png

Rzutujemy na ten typ, bo nie mamy pewności, że po tym kluczem ze słownika kryje się obiekt typu IWebDriver. Po tym kroku przechodzimy do następnej metody.

2.16.png

W tej metodzie przechodzimy naszym WebDriverem do konkretnie wybranej strony. Dodaję if’a, ponieważ w parametrze podajemy home (jeśli podamy jako parametr inną wartość w parametrze niż home, nasz test zwróci błąd).

Następnie przechodzimy do zadanej strony poprzez użycie metody GoToUrl();

Kolejnym "step", który zaimplementujemy, będzie krok kliknięcie w dowolną pozycję w menu z naszej testowej strony.

2.17.png

Zaczynamy od dodania zmiennej, która zawiera listę wszystkich pozycji menu - czyli zmienna menuElements.

2.18.png

O szukaniu elementów napisałem kiedyś post (link). Nie chce się powtarzać więc pokrótce - każdym element na stronie możemy zlokalizować przy pomocy tzw. lokatorów (ID, Name, CSS Selector, XPath, PartialLink, LinkText).

W tym miejscu użyłem dodatku SelectorGadget – plugin do Chrome, który pozwala nam szukać w przyjemny sposób interesujących nas locatorów. Możemy również szukać w trybie programisty tego co nas interesuje (F12 w Chrome), a w SelectorGadget szybko sprawdzić, czy lokator, który wybraliśmy jest tym czego szukamy, gdyż ta wtyczka podświetla i zlicza te elementy, które podamy w polu jako wartość lokatora.

Po dodaniu zmiennej z listą elementów przechodzimy do dodania switch wraz z różnymi opcjami. Dodajemy implementację dla Contact.

2.19.png

Szukamy w menu opcji: Contact, klikamy. W kodzie umieściłem również asercję (zaczynającą się od Assert.True), która sprawdza nam, czy obecna wartość właściwości driver. Url zmieniła się po naszym wcześniejszym kliknięciu.

Jeżeli Url nie zmieniłby się, to nasz test zatrzymałby się w miejscu asercji i zwróciłbym błąd. Mamy również możliwość dodawania naszych własnych komunikatów błędów, jeżeli dana asercja nie zgadza się. Pozwala to na szybsze znajdywanie błędu w teście, jeżeli dana asercja spowoduje błąd.

Gdy mamy już metodę, która pozwala nam wchodzić w dowolną pozycję menu, musimy dodać implementację metody, która będzie wypełniać nasz formularz kontaktowy.

2.20.png

2.21..png

W pierwszych pięciu linijkach metody, tak jak w poprzednim kroku dodajemy obiekty IWebElement, które posiadają lokator interesujących nas elementów.

Metoda SendKeys (””) - pozwala za pomocą Selenium WebDriver wysłać interesujący nas tekst, w danym element. W teście chcemy dodać prosty pierwszy komentarz.

Następnie używamy metody Click() w obiekcie submitButton, który reprezentuje obiekt (metoda Click() pozwala kliknąć interesujący nas IwebElement).

2.22..png

Pozostaje nam jeszcze dodanie metody, która będzie sprawdzała nam poprawność pozytywnego wysłania wiadomości z formularza kontaktowego. W naszym wypadku jest to Message Sent (go back).

2.23.png

Tworzymy IWebElement, który ma być reprezentacją tego tekstu po udanym wysłaniu formularza. Dodatkowo dodajemy asercję, sprawdzającą, czy parametr message jest równy z tym tekstem, który będziemy mieć wysłaniu formularza.

W tym miejscu możemy uruchomić nasz test. Przechodzimy do zbudowania projektu, poprzez kliknięcie: Ctrl + Shift + B lub  prawego przycisku myszy na projekcie, a następnie wybranie Build. Jeżeli wszystko będzie okej naszym oczom ukaże się komunikat:

2.24.png

W VS z menu wybieramy  ReSharper (R#)

2.25.png

2.26.png

Z rozwiniętego menu wybieramy: Unit Tests. Następnie ponownie Unit Tests.

Pokaże nam się okno, w który mamy wszystkie testy, znalezione przez R# w naszym projekcie. Nazwa unit test wywodzi się z tego, że R# dzieli testy na runnery, a jak wiemy w NUnit czy XUnit możemy pisać testy jednostkowe, integracyjne oraz akceptacyjne, więc NUnit służy jako test runner zadanych testów,w naszym przypadku akceptacyjnych.

2.27-e1515486178246.png

Naszym oczom ukazuje się test, który zakończył się pozytywnym wynikiem.Może się zdarzyć, że Twój test będzie miał błąd. Dlaczego? Dlatego, że nie dodaliśmy timeoutów (opowiem o tym w przyszłym wpisie)

Teraz przechodzimy do nauki podstaw debugowania.  Znajdujemy się w pliku CheckContactForm.feature Specjalnie dodajemy błąd do naszego testu: zamiast parametru “home” zmieniamy w naszym teście na “Home”.

Uruchamiamy nasz test.

2.28-e1515486317363.png

Otrzymujemy informację, że nasza zmienna ma wartość null, a nie może jej zawierać metoda, która przechodzi do strony.

Co teraz musimy zrobić?

Przechodzimy do ContactFormSteps.cs do metody pierwszej.

Dodajemy tzw. breakpointa, który pozwoli nam uruchomić nasz test w trybie debug, czyli każda linijka będzie możliwa do uruchamienia krok po kroku.

2.29.png

Aby dodać breakpointa musimy kliknąć dwukrotnie na obszar, który wskazany jest kursorem oczywiście przy linijce, która nas interesuje (w tym przypadku jest to linijka [19].).

2.30.png

Wybieramy Debug Unit Tests.

2.31.png

Naszym oczom ukaże się sytuacja, gdy VS wszedł do naszej metody przed inicjalizacją zmiennej URL. Możemy dodać do watcha tą zmienną i obserwować jej zmienianie.

2.32..png

Klikamy F10 przechodzimy do następnej linijki.

Gdy najedziemy na zmienną name zobaczymy, że jej wartością jest Home zamiast home i instrukcja warunkowa (if) zostanie pominięta, więc VS wejdzie w linijkę [22] gdzie nasza zmienna url ma wartość null, a to zakończy się błędem.

2.33..png

W tej części wpisu zobaczyliśmy jak łatwo możemy dowiedzieć się, gdzie popełniliśmy błąd. W następnym wpisie ulepszymy ten krok. Gdy używamy stringa jako wartość do porównania poprawności z inną, łatwiej jest o błąd niż np.  z enumem.

Pliki, które tworzymy, gdy korzystamy ze SpecFlow:

  • Hooks - klasa, która może zawierać metody, uruchamiane w określonych sytuacjach:
  • NazwaPliku.feature - plik, w którym są scenariusze powiązane z konkretnym featurem.
  • StepDefinition - jest to klasa, którą dodajemy wraz ze zdefiniowanym implementacjami i deklaracjami poszczególnych metod kroków.
  • [Binding] - ważny atrybut, po którym SpecFlow widzi, że dana klasa ma być wzięta pod uwagę przez SpecFlow np. klasa z implementacjami poszczególnych kroków.  Po zakomentowaniu tej linijki scenariusz znów staje się fioletowy.

2.34.png

Podsumowanie

W dzisiejszym wpisie napisaliśmy nasz pierwszy test przy użyciu SpecFlow wraz z Selenium WebDriver oraz C#. Dodatkowo nauczyliśmy się podstaw jednej z najważniejszych umiejętności każdego programisty, jaką jest debugowanie. W następnym wpisie dokonamy ulepszenia (refactoringu) kodu naszych testów oraz dodamy go do repozytorium kodu. Zachęcam do komentowania i udostępniania dzisiejszego wpisu. Jeżeli masz jakieś pytania chętnie na nie odpowiem - [email protected].