Automaty do zadań specjalnych Olga Maciaszek-Sharma, Artur Kotow Wersja 1, 13.05.2014
Agenda Kilka pytań do publiczności Po co się męczyć? Studium przypadku Olympus Wprowadzenie Opis rozwiązania Wnioski Studium przypadku Inmarsat Wprowadzenie Opis rozwiązania Wnioski Podsumowanie Outbox Commercial in Confidence 04 October 2014
Kilka pytań do publiczności Kto z Was zajmuje się automatyzacją? Kto z Was nagrywa scenariusz przy użyciu REC w swoim narzędziu? Kto z Was samodzielnie pisze kod w używanym narzędziu? Kto z Was tworzy własne narzędzia? Ręka do góry
Po co się męczyć? Wyważanie otwartych drzwi? Bariery wejścia Wyzwania Technologiczne Wyzwania biznesowe, czyli trzeba przekonać klienta Rezultaty biznesowe Rozwój kompetencji
Studium przypadku Olympus Branża medyczna Wysokie wymagania jakościowe Integracja z urządzeniami medycznymi Aplikacja napisana w Javie, wykorzystująca GWT i Springa Ponad 200 procesów Ponad 1000 scenariuszy testowych Ciągła regresja Projekt w metodyce WAGILE Ewolucja automatyzacji
Dlaczego nie Selenium IDE?
Nasze rozwiązanie - pakiety Podział klas na pakiety: data, pages, tests Dane Obiekty stron Skrypty testowe
Nasze rozwiązanie - klasy danych Klasy pozwalająca tworzyć obiekty zawierające dane wykorzystywane do pracy z formularzami
Nasze rozwiązanie klasa SeleniumBrowser Opakowuje WebDriver Oddzielne metody na często wykonywane operacje: Wyszukiwanie elementów Wybieranie elementów na listach Wpisywanie tekstu Obsługa alertów Oczekiwanie na załadowanie się danego elementu Obsługa alertów Obsługa debugid
Nasze rozwiązanie klasa SeleniumBrowser public void writetextbygwtid(string fieldid, String value) { WebElement webelement = bygwtid(fieldid); if (!webelement.gettagname().equalsignorecase("input") &&!webelement.gettagname().equalsignorecase("textarea")) { webelement = webelement.findelement(by.cssselector("input,textarea")); writetext(webelement, value); Wprowadzanie tekstu public boolean acceptalertifpresent() { try { driver.switchto().alert().accept(); return true; catch (NoAlertPresentException Ex) { return false; Akceptowanie alertów
Nasze rozwiązanie klasa TableHelper Tabele identyfikowane za pomocą debugid Metody zwracające zawartość komórek Metody zwracające wiersze tabeli ze zdefiniowaną zawartością Metody zliczające rekordy Obsługa sortowania i stronicowania
Nasze rozwiązanie klasa TableHelper Zwracanie zawartości komórki public String getcelldata(webelement row, int columnindex) { List<WebElement> divs = rowcelldivs(row); WebElement cell = divs.get(columnindex); return cell.gettext(); Zwracanie zawartości wiersza public String[] columncellcontents(integer columnindex) { List<WebElement> rows = allrows(); System.out.println("Got all rows"); String[] result = new String[rows.size()]; for (int i = 0; i < rows.size(); i++) { WebElement rowelement = rows.get(i); result[i] = rowcellcontents(rowelement)[columnindex]; System.out.println("Found cells: " + result.length); return result;
Nasze rozwiązanie klasa TableHelper Wyszukiwanie wierszy na podstawie zawartości konkretnej kolumny List<WebElement> findrowswithmatchingcell(cellmatcher cellmatcher, Integer columnindex) { List<WebElement> result = Lists.newArrayList(); for (WebElement row : allrows()) { List<WebElement> divs = rowcelldivs(row); if (cellmatcher.matches(divs.get(columnindex))) { result.add(row); return result;
Nasze rozwiązanie klasy stron Wykorzystanie modelu Page Objects Klasy reprezentują widoki aplikacji i pozwalają na wykonywanie operacji dostępnych na danym widoku, np. wypełnienie formularza, pobranie wartości danego pola, etc. Wszystkie strony dziedziczą z AbstractPage Obiekt SeleniumBrowser przekazywany jako argument Metody kończące się zmianą widoku zwracają obiekt nowoutworzonej strony
Nasze rozwiązanie klasy stron W każdej klasie strony metody pozwalające na realizację odpowiednich akcji z danego widoku public EditOrderPage openeditorderpageviaorderdetails() { viewfirstorderdetailsviaactions(); browser.find(by.linktext("edytuj")).click(); browser.waitfortext("edycja zamówienia"); return new EditOrderPage(browser);
Nasze rozwiązanie klasy testów Klasy testów zgrupowane w pakiety odpowiadające obszarom aplikacji Wszystkie testy dziedziczą z klasy AbstractGuiTest Operacje logowania i wylogowania użytkowników Pobranie danych z pliku properties Ustawienie domyślnych wartości WebDriver a Testy obszarów dziedziczą dodatkowo z klas abstrakcyjnych dla danego obszaru Generowanie danych
Nasze rozwiązanie klasy testów @Test public void shoulddisplaycomponentdata() { // Given login(labuser, labpassword); final FoldersForDiagnosisPage foldersfordiagnosispage = menuhelper.gotofoldersfordiagnosispage(); if(foldersfordiagnosispage.getfirstfoldercode()!= null){ final EditDiagnosisPage editdiagnosispage = foldersfordiagnosispage.opendiagnosisforfirstfolder(); Czytelne nazwy obiektów stron i metod // When editdiagnosispage.scanfirstcomponent(); // Then asserttrue(browser.textispresent("opis makroskopowy")); asserttrue(browser.textispresent(diagnosisreferraldata.getmacroscopedescription())); else { getfolderpackagefordoctor(); shoulddisplaycomponentdata(); Asercje
Nasze rozwiązanie klasy testów @Test public void shouldstopgrossingreferral() throws InterruptedException { // Given login(labuser, labpassword); ReferralsForGrossingPage referralsforgrossingpage = menuhelper.gotogrossingandchoosepartner(); final String referralcode = referralsforgrossingpage.getfirstelementtogross(); if (referralcode!= null) { final ReferralGrossingPage referralgrossingpage = referralsforgrossingpage.startgrossing(referralcode); referralgrossingpage.createstoragebinifnotexists(); //When referralsforgrossingpage = referralgrossingpage.stopgrossing(); //Then String status = referralsforgrossingpage.getelementstatusbycode(referralcode); assertequals(in_grossing, status); else { logout(); generatereferraltogross(); shouldstopgrossingreferral(); Jeżeli brakuje danych, generujemy je metodą z klasy abstrakcyjnej i ponownie uruchamiamy test
Nasze rozwiązanie klasy testów public abstract class AbstractLaboratoryTest extends AbstractGuiTest { String generatereferraltogross() throws InterruptedException { login(officeuser, officepassword); ClientTasks clienttasks = new ClientTasks(menuHelper, generator); String referralcode = clienttasks.createreferralwithtissuecontainer(); dispatchandcollectreferral(clienttasks, referralcode); logout(); return referralcode; Jeżeli brakuje danych, generujemy je metodą z klasy abstrakcyjnej i ponownie uruchamiamy test
Dlaczego nie Selenium IDE? Nieczytelne testy-tasiemce wbrew wszystkim dobrym praktykom programistycznym Używanie zmiennych oznaczeń do identyfikacji elementów na stronach brak odporności na zmiany Brak waitów przy odtwarzaniu testy notorycznie się wywalają Konieczność dodatkowej obsługi zmiennych wartości Konieczność dodatkowej obsługi asercji
Wnioski Jakie zalety i wady tego podejścia zauważacie? - Lepsza jakość kodu - Większa kontrola - Łatwość utrzymania - Łatwiejsza identyfikacja błędów - Tworzenie frameworku testowego i skryptów bardziej czasochłonne - Wymaga większych kompetencji
Studium przypadku Inmarsat Branża telko Częste wydania Internet satelitarny Aplikacja oparta na PeopleSoft CRM 120 komunikatów do regresji Wysokie wymagania jakościowe Ciągła regresja Projekt w metodyce Waterfall Różne cele automatyzacji
Dlaczego nie testy po GUI? Dostępne środowiska testowe o słabej wydajności Specyfika klienta przeważające wykorzystanie (ok. 80%) plików XML do wprowadzania zmian w bazie systemu PeopleSoft, a tylko ok. 20% operacji wykonywanych z poziomu GUI
Nasze rozwiązanie klasy wzorców Wykorzystanie SimpleFramework XML Klasy tworzące szkielety poszczególnych powtarzalnych elementów wykorzystywanych w XML ach Określenie struktury elementów XML i poprzez wykorzystanie adnotacji i odpowiednie zdefiniowanie konstruktorów
Nasze rozwiązanie klasy wzorców @Root(name = "Package") public class Package { @ElementList(name = "Parameter", inline = true) protected final List<Parameter> packageparameters; Elementy modułu Package z adnotacjami Element i ElementList @Element(name = "PackageAttributes", required = false) protected PackageAttributes attributes; @ElementList(inline = true) protected final List<Service> services; Konstruktor public Package(PackageAttributes attributes, PackageParameters packageparameters, List<Service> services) { this.attributes = attributes; this.packageparameters = packageparameters.packageparameters; this.services = services;
Nasze rozwiązanie klasy wzorców @Root(name = "PackageAttributes") public class PackageAttributes { Klasa reprezentująca moduł PackageAttributes zawarty w Package z poprzedniego slajdu @ElementList(inline = true, required = false) protected final ArrayList<Parameter> parameters; public PackageAttributes(ArrayList<Parameter> parameters) { this.parameters = parameters;
Nasze rozwiązanie klasy generatorów Pozwalają tworzyć gotowe XMLe na podstawie klas szkieletów Każdy generator dziedziczy z klasy AbstractBusinessRequestBuilder W konstruktorze przekazywany jest obiekt klasy BusinessRequestData zawierający dane biznesowe Każdy generator tworzy konkretny typ XMLa złożony z klocków zdefiniowanych w klasach - wzorcach
Nasze rozwiązanie klasy generatorów public class ActivatePackageBusinessRequestBuilder extends AbstractBusinessRequestBuilder { public ActivatePackageBusinessRequestBuilder(BusinessRequestData businessrequestdata) { super(businessrequestdata); Obiekt zawierający dane Implementacja metody abstrakcyjnej z nadklasy @Override protected BusinessData createbusinessdata() { Parameter parameter = new Parameter("CustomerId", businessrequestdata.getcustomerid()); return new BusinessData(parameter, createpackage()); protected Package createpackage() { PackageData packagedata = businessrequestdata.getpackagedata(); Parameter imsi = new Parameter("IMSI", packagedata.getimsi()); Parameter iccid = new Parameter("ICC-ID", packagedata.geticcid()); return new Package(createPackageAttributes(packageData), createpackageparameters(packagedata.getpackageid(), packagedata.getproductid()), createbulkservice(imsi, iccid), createservicepackages(packagedata.getproductid()));
Nasze rozwiązanie zasilanie XMLi danymi Dane do testów pobierane z baz PeopleSoft a działających na środowiskach testowych Rezultaty testów i kluczowe dane zapisywane w specjalnie utworzonej bazie testowej Odpowiednie klasy DataAccessObject i DataTransferObject
Nasze rozwiązanie zastosowania i statystyki Skrypty testowe do Regresji z wykorzystaniem frameworku JUnit generowanie XMLi, przepuszczanie przez system i analiza odpowiedzi 120 przypadków testowych Uruchamiane co tydzień Zrealizowana regresja 4 release ów Generowanie danych do testów wydajnościowych Generowanie jednorazowo po 1000 plików XML 13 104 plików XML wygenerowane w trakcie 4 release ów Wygenerowanie paczki 1000 XML i z pobraniem danych z bazy PS i zapisem do bazy testowej -> ok. 25min Outbox Commercial in Confidence 04 October 2014
Wnioski Jakie zalety i wady tego podejścia zauważacie? - Dostosowanie testów do specyfiki wykorzystania systemu przez klienta - Prosta i szybka generacja danych testowych - Stworzenie rozwiązania znacznie mniej czasochłonne niż przygotowanie dobrych skryptów do testów po GUI - Zespół rozwijający i utrzymujący narzędzie musi mieć odpowiednie kompetencje programistyczne
Podsumowanie Rozwiązania dostosowane do specyfiki danego projektu Rozwiązania łatwe w utrzymaniu Wyższy próg wejścia Nie zawsze warto stosować... warto rozważyć
olga.maciaszek-sharma@outbox.pl artur.kotow@outbox.pl