Jak pisać testy? Wersja 2



Podobne dokumenty
Mazowiecki Elektroniczny Wniosek Aplikacyjny

LeftHand Sp. z o. o.

Instrukcja rejestracji w systemie System Wspierający Prowadzenie Prac Badawczo-Naukowych oraz Współdzielenie i Publikację Wyników Prac

Instalacja systemu zarządzania treścią (CMS): Joomla

Instrukcja zarządzania kontem przedsiębiorstwa w serwisie internetowym

Instrukcja zarządzania kontem jednostki samorządu terytorialnego w serwisie internetowym

epuap Zakładanie konta organizacji

INSTRUKCJA OBSŁUGI Proces rejestracji i logowania

epuap Zakładanie konta organizacji

INSTRUKCJA. zakładania konta w Społeczności CEO oraz rejestrowania się do programu lub na szkolenie/cykl szkoleniowy KROK 1

Biblioteki publiczne

INSTRUKCJA UŻYTKOWNIKA GENERATORA WNIOSKÓW O DOFINANSOWANIE DLA WNIOSKODAWCÓW

Platforma e-learningowa

Repozytorium Cyfrowe BN

ELEKTRONICZNA KSIĄŻKA ZDARZEŃ

Elektroniczna Skrzynka Podawcza

Procedura krok po kroku umożlwiająca logowanie kontem MCL w POL-on (dla aktualnych użytkowników POL-on) Rejestracja nowego użytkownika w systemie MCL

Instrukcja portalu TuTej24.pl

INSTRUKCJA. rejestrowania się na szkolenie/cykl szkoleniowy oraz uzupełniania niezbędnej unijnej dokumentacji uczestnictwa w projekcie (PEFS)

INSTRUKCJA zakładania konta w Społecznoś ci CEO

PTP COL. Instrukcja obsługi

Internetowy System Zgłoszeń Postępowanie Kwalifikacyjne w Służbie Cywilnej PRZEWODNIK

Instrukcja obsługi portalu MojeHR moduł pracownika

Testowanie aplikacji. Kurs języka Ruby

Instrukcja. Elektronicznej Skrzynki Podawczej

Podręcznik Użytkownika LSI WRPO

INSTRUKCJA SKŁADANIA OFERT W SYSTEMIE WITKAC.PL

Tworzenie konta Na stronie głównej klikamy w przycisk Zarejestruj się

1. Doradcy Logowanie i Pulpit Mój profil Moje kwalifikacje Moi klienci Szczegóły klientów...

Instrukcja rejestracji do systemu ecrf.biztm

Programowanie w Ruby

Biblioteki publiczne


Podręcznik użytkownika Wprowadzający aplikacji Wykaz2

Jak złożyć wniosek o dotację Fundacji PZU? - instrukcja dla użytkownika

INSTRUKCJA UŻYTKOWNIKA GENERATORA WNIOSKÓW O DOFINANSOWANIE DLA WNIOSKODAWCÓW

Instrukcja zamawiania usług systemu ASG-EUPOS za pomocą Portalu PZGiK

INSTRUKCJA UŻYTKOWNIKA GENERATORA WNIOSKÓW O DOFINANSOWANIE DLA WNIOSKODAWCÓW

darmowe zdjęcia - allegro.pl

1.2 Prawa dostępu - Role

Instrukcja logowania i użytkowania platformy Uniwersytet Przedsiębiorczości

REJESTRACJA I PUBLIKACJA ARTYKUŁÓW W SERWISIE. TUTORIAL

Miejski System Zarządzania - Katowicka Infrastruktura Informacji Przestrzennej

epuap Archiwizacja w Osobistym Składzie Dokumentów

Spis treści REJESTRACJA NOWEGO KONTA UŻYTKOWNIKA PANEL ZMIANY HASŁA PANEL EDYCJI DANYCH UŻYTKOWNIKA EXTRANET.NET...

1. Rejestracja 2. Logowanie 3. Zgłaszanie nowego wniosku projektowego

HOTSPOT. [ konfiguracja, rejestracja, użytkowanie ]

Instrukcja Użytkownika Systemu Zarządzania Tożsamością Wersja. 1.0

Extranet KLUBY.PZPN.PL Instrukcja obsługi

Skrócona instrukcja pracy z Generatorem Wniosków

Instrukcja dla Kandydatów na staż w Projekcie SIMS

Internetowy System Składania Wniosków PISF wersja 2.2. Instrukcja dla Wnioskodawców

Podręcznik użytkownika Publikujący aplikacji Wykaz2

Tworzenie dokumentów oraz prezentacji programu Młodzi Aktywiści Prezydencji przy wykorzystaniu EduTuby

1. Składanie wniosku rejestracyjnego - rejestracja konta użytkownika/firmy

ZESTAW PLATINUM. - instrukcja pobrania i instalacji certyfikatu niekwalifikowanego wersja 1.2

Instrukcja obsługi systemu MEDIABIN Grudzień 2012

INSTRUKCJA KROK 1. UWAGA: Jeżeli posiadasz już konto w Społeczności CEO, przejdź do kroku 9, pomijając część dotyczącą tworzenia konta w Społeczności.

AKTYWNY SAMORZĄD. Instrukcja użytkownika.

INSTRUKCJA OBSŁUGI BAZY USŁUG ROZWOJOWYCH


Kalipso wywiady środowiskowe

INSTRUKCJA TWORZENIA KONTA W SYSTEMIE WITKAC.PL

1. Klienci Logowanie i Pulpit Mój profil Wyniki testów Moje CV Kapitał Kariery...

Instrukcja zakładania konta pocztowego na stronie www-tz.c.pl

Instrukcja podłączenia do ZSMOPL na środowisku produkcyjnym

Poradnik użytkowania strony internetowej Sprawnego Dolnoślązaczka

Instrukcja podłączenia do ZSMOPL na środowisku produkcyjnym

System epon Dokumentacja użytkownika

VENUS-BEAUTY.pl. Instrukcja obsługi procesu zamówienia

Instrukcja obsługi Zaplecza serwisu biznes.gov.pl dla Pracowników Instytucji w zakresie weryfikacji opisów procedur przygotowanych przez Zespół epk

Instrukcja użytkownika. Instrukcja konfiguracji i obsługi modułu e-rejestracja

Platforma e-learningowa

INSTRUKCJA UŻYTKOWNIKA GENERATORA WNIOSKÓW O DOFINANSOWANIE DLA WNIOSKODAWCÓW

Instrukcja korzystania z Systemu Telnom - Nominacje

SUPLEMENT DO DYPLOMU

Droga Nauczycielko, Nauczycielu praktykujący OK zeszyt ;-) Witamy Cię w Społeczności CEO.

INSTRUKCJA zakładania konta w Społeczności CEO

APLIKACJA KONKURSOWA INSTRUKCJA UŻYTKOWNIKA

1 z 18. Spis treści: 1. Zakładanie konta na portalu OX.PL

Instrukcja Integracja z istore. Wersja z 07/02/2015. Copyright Zakupteraz.pl

Instrukcja obsługi rejestrowanie uczestników rajdu.

Zakładanie konta użytkownika na platformie do nauczania na odległość

Instrukcja użytkownika

Instrukcja rejestracji

INFORMACJE OGÓLNE. Użytkownik aplikacji otrzymuje dostęp do aktualnych informacji dotyczących obiektu

Facebook, Nasza klasa i inne. podstawowe informacje o serwisach społeczności internetowych. Cz. 2. Facebook

Portal Personelu Medycznego Global Services Sp. z o.o.

Część 3 - Konfiguracja

Instrukcja obsługi Zaplecza epk w zakresie zarządzania tłumaczeniami opisów procedur, publikacji oraz poradników przedsiębiorcy

2. Kliknięcie Złóż wniosek otworzy Panel wnioskodawcy, o następującym wyglądzie

Certyfikat kwalifikowany

Instrukcja aktywacji i instalacji Certum Code Signing

INSTRUKCJA OBSŁUGI. Proces rejestracji i logowania

INSTRUKCJA instalacji aplikacji elisty.pl

INSTRUKCJA Panel administracyjny

Skrócona instrukcja obsługi moduł lekarza

wersja 1.0 ośrodek komputerowy uj cm ul. mikołaja kopernika 7e, Kraków tel

1. REJESTRACJA W INTERIM24.PL PANEL UŻYTKOWNIKA ZAWARTOŚĆ UZUPEŁNIENIE PROFILU... 9

Transkrypt:

Jak pisać testy? Wersja 2 Jakub Stolarski 2011-11-28 Spis treści 1 Wymagania i 1.1 Instalacja................................................. i 2 Testy akceptacyjne ii 2.1 Przygotowanie danych do testów..................................... ii 2.2 Przygotowanie testów........................................... iii 3 Testy funkcjonalne v 3.1 Testowanie javascript........................................... viii 4 Test driven development xi 1 Wymagania Do testów oprócz Django potrzebne są biblioteki: Django-WebTest 1, WebTest 2, AllPairs 3 perf_utils, selenium 4. 1.1 Instalacja Instalacja zewnętrznych bibliotek za pomocą pip odbywa się poprzez wpisanie: 1 http://pypi.python.org/pypi/django-webtest 2 http://webtest.pythonpaste.org/en/latest/index.html 3 http://sourceforge.net/apps/trac/allpairs/ 4 http://pypi.python.org/pypi/selenium

pip install webtest pip install django-webtest pip install AllPairs pip install selenium Zamiast pip install można użyć easy_install. Perf_utils znajduje się w naszym svn w django_project_templates (jak tak jak cms_project) ściągamy z svn: svn co svn://80.52.177.82/django_project_templates I uzupełniamy jeden z plików.pth w site-packages ścieżkami wskazującymi na katalogi: django_project_templates/src django_project_templates/src/cms_project Aplikację perf_utils należy dodać do INSTALED_APPS w pliku local_settings.py. 2 Testy akceptacyjne W MowimyJakV1 w pliku cms_local/acceptance_tests.py znajduje się przykładowy test akceptacyjny opisujący podstawowy przebieg tworzenia i publikowania artykułu w serwisie www.mowimyjak.pl. Opis przypadku testowego: admin lub kierownik definiują nowe zadanie (artykuł z kategorią i tytułem) redaktor widzi listę nowych zadań i pobiera interesujący go temat redaktor pisze tekst i przesyła go do sprawdzenia do admina (długość tekstu musi wynosić minimum 1000 znaków) kierownik otrzymuje podgląd artykułu przy sprawdzeniu admin może: opulikować artykuł lub zwrócić go do redaktora W tym przypadku testowym mamy 3 role admin, kierownik oraz redaktor. 2.1 Przygotowanie danych do testów Na początek wypełnijmy testową bazę. Uruchamiamy aplikację na testowej bazie: python manage.py testserver Wchodzimy do panelu administracyjnego i tworzymy użytkowników dla odpowiednich ról. Nazwijmy ich superredaktor1 (admin), kierownik1 (kierownik), redaktor1 (redaktor). Oprócz ról tworzymy potrzebne kategorie, grupy użytkowników, uprawnienia itp. Jeżeli mamy już działającą bazę można ją skopiować. Utworzona baza ma prefix test_ (możemy dodać w konfiguracji baz danych taką baze z aliasem test_mowimyjak, aby wygodniej operować na bazie danych). Możemy zgrać jej zawartość do plików json, które wykorzystamy jako fixture w naszych testach: python manage.py dumpdata --database test_mowimyjak auth articles.articleblocktype \ > cms_local/fixtures/auth.json python manage.py dumpdata --database test_mowimyjak categories \ > cms_local/fixtures/categories.json python manage.py dumpdata --database test_mowimyjak authors > cms_local/fixtures/authors.json python manage.py dumpdata --database test_mowimyjak agency > cms_local/fixtures/agency.json python manage.py dumpdata --database test_mowimyjak editorprofiles.editorial \

> editorprofiles/fixtures/editorial.json python manage.py dumpdata --database test_mowimyjak editorprofiles.editorprofile \ > editorprofiles/fixtures/editorprofiles.json 2.2 Przygotowanie testów Następnie tworzymy szablon testów: python manage.py gen_tests --template acceptance > acceptance_tests.py W szablonie uzupełniamy fixtures o ścieżki do wcześniej wygenerowanych fixtures i tworzymy klasę dla naszego testu: class AbstractMuratorTest(AcceptanceTestCase): fixtures = [ cms_local/fixtures/auth.json, cms_local/fixtures/categories.json, cms_loca/fixtures/authors.json, cms_loca/fixtures/agency.json, editorprofiles/fixtures/editorial.json, editorprofiles/fixtures/editorprofiles.json, ] class BasicWorkflowTest(AbstractMuratorTest): Teraz zróbmy test sprawdzający poprawność dodawania i publikacji artykułu. Metdy testów muszą zaczynać się od prefiksu test_: class BasicWorkflowTest(AbstractMuratorTest): def test_add_article(self): task_num = 1 self.task_title = test_zadanie%s % task_num # dodanie zadania przez kierownika self._supervisor_add_task() # dodawanie artykulu przez redaktora self._editor_add_article() # kierownik ma wgląd do artykulu self._supervisor_view_article() # zatwierdzenie artykulu przez administratora self._admin_publish_article() Przygotowaliśmy sobie pomocniczą zmienną task_title, która będzie tytułem zadania, a następnie artykułu. Test podzieliliśmy dla wygody na części wykonywane poprzez poszczególne role. Uzupełnijmy teraz część testu przypisaną do roli kierownika: def _supervisor_add_task(self): ## kierownik loguje sie do panelu kierownika response = self.app.get( /panel-kierownika/ ) response.form[ username ] = kierownik1 response.form[ word ] = test response = response.form.submit().follow()

## klika dodaj zadanie response = response.click(href= articles/task/add/ ) ## wypelnia formularz response.form[ title ] = self.task_title response.form[ category ] = 22 ## klika na zapisz response = response.form.submit().follow() self.assertequals(response.status_code, 200) try: self.task = Task.objects.get(title=self.task_title) except Task.DoesNotExist: self.task = None self.assertfalse(self.task is None) ## i wylogowuje sie response.click(href= /panel-kierownika/logout ) Pod zmienną self.app znajduje się testowana aplikacja (więcej informacji w dokumentacji WebTest 2 ). Otwieramy stronę poprzez get, pobieramy jedyny znajdujący się tam formularz, uzupełniamy go i logujemy się. Ponieważ po poprawnym zalogowaniu następuje przekierowanie, to dorzucamy follow. Następnie klikamy na przycisk dodawania zadania, wypełniamy formularz i tworzymy nowe zadanie. Nowo utworzone zadanie zapisujemy, aby móc odwołać się do tego samego zadania w dalszej części testu. Na koniec wylogowujemy się. Analogicznie postępujemy dla pozostałych ról. Następnie wyciągamy testy z naszej klasy (aktualnie tylko jeden test) i tworzymy z nich zestaw testów, który podpinamy do głownego zestawu testów: basic_suite = test_loader.loadtestsfromtestcase(test_case) def suite(): suite = unittest.testsuite() test_suites = [ basic_suite ] test_loader = unittest.testloader() for test_suite in test_suites: suite.addtest(test_suite) return suite W tej samej klasie testów możemy i powinniśmy zdefiniować inne testy sprawdzające alternatywne ścieżki przebiegu (np. odrzucenie artykułu przez kierownika). Django automatycznie uruchamia jedynie te testy, które podpięte są w pliku tests.py w katalogu aplikacji. Dlatego musimy zaimportować tam nasze testy: import unittest def suite(): from cms_local.acceptance_tests import suite as acceptance_suite suite = unittest.testsuite() test_suites = [ acceptance_suite() ] for test_suite in test_suites: suite.addtest(test_suite) return suite Testy uruchamiamy poleceniem:

python manage.py test cms_local 3 Testy funkcjonalne W MowimyJakV1 w pliku cms_local/functional_tests.py znajduje się przykładowy test funkcjonalny sprawdzający działanie formularza tworzenia artykułu dla redaktora serwisu www.mowimyjak.pl. Opis przypadku testowego: jako zalogowany redaktor pobieramy zadanie, na podstawie którego tworzymy artykuł wypelniamy pola zajawka, tagi, treść, nadtytul, zdjęcie główne, galeria klikamy Zapisz i wyślij do sprawdzenia w wyniku otrzymujemy potwierdzenie, że artykuł został wysłany i trafiamy na strone główną panelu redaktora Alternatywne przypadki do przetestowania: uzupełniona zbyt długa zajawka brak zajawki brak tagów brak treści za długi nadtytuł Na początek generujemy szablon testów funkcjonalnych: python manage.py gen_tests --template functional > functional_tests.py Uzupełniamy szablon przez wcześniej wygenerowane fixtures oraz naszą klasą testową AddArticleTest. W każdym teście bedziemy potrzebować zadania, na podstawie którego powstanie artykuł. Dlatego w metodzie setup tworzymy takie zadanie i przypisujemy je pod self.task, aby łatwiej odwołać się do niego w testach. W częsci teardown powinniśmy wyczyścić wszystko to, co utworzyliśmy w setup (czyli nasze zadanie): class AbstractMuratorTest(AcceptanceTestCase): fixtures = [ cms_local/fixtures/auth.json, cms_local/fixtures/categories.json, cms_loca/fixtures/authors.json, cms_loca/fixtures/agency.json, editorprofiles/fixtures/editorial.json, editorprofiles/fixtures/editorprofiles.json, ] class AddArticleTest(AbstractMuratorTest): def setup(self): super(addarticletest, self).setup() task_num = 1 self.task_title = test_zadanie%s % task_num # tworzymy zadanie supervisor = User.objects.get(username= superredaktor1 ) category = Category.objects.get(pk=22) self.proposal = TaskProposal.objects.create(title=self.task_title, status=taskproposal.status self.task = Task(title=self.task_title) self.task.category = category

self.task.user = User.objects.get_or_create(username=settings.NOT_SET_USERNAME)[0] self.task.status = Task.STATUS_NEW self.task.save() def teardown(self): # usuwamy artykul self.task.delete() self.proposal.delete() super(addarticletest, self).teardown() Następnie tworzymy pomocniczą metodę, która zasymuluje pobranie zadania przez zalogowanego redaktora, wypełni formularz, zapisze i wyśle do sprawdzenia: def _add_article(self, lead, tags, text, overtitle, lead_photo, gallery): editor = User.objects.get(username= redaktor1 ) response = self.app.get( /panel-redaktora/articles/task/take/%s/ % self.task.id, user=editor) response = response.follow() response.form[ lead ] = lead response.form[ tags ] = tags response.form[ text ] = text response.form[ overtitle ] = overtitle response.form[ lead_photo ] = lead_photo response.form[ gallery ] = gallery return response.form.submit( _save_and_sent_to_check, 0) Pod self.app mamy utworzony obiekt aplikacji WebTest, przez który możemy wykonywać zapytania na naszej aplikacji. W tym teście wywołujemy metodę get, która pobiera odpowiednie zadanie. Opis pozostałych metod (m.in. post) znajduje się w dokumentacji WebTest 2. Jako parametr user podaliśmy obiekt redaktora. Takie użycie powoduje, że zapytanie jest traktowane, jakbyśmy byli zalogowani jako ten użytkownik. W wyniku zapytania otrzymujemy obiekt response. Ponieważ pobranie zadania powoduje przekierowanie, to podążamy za przekierowaniem przy użyciu metody follow. W wyniku otrzymujemy kolejny obiekt response. W obiekcie response pod zmienną form znajduje się pierwszy formularz znaleziony na stronie. Jeżeli formularzy byłoby więcej, to wszystkie znajduja się w słowniku forms. Formularz zachowuje się jak słownik i możemy zupełnić poszczególne pola podpisując je pod kluczami, które odpowiadają artybutom name w formularzu. Na koniec wysyłamy formularz za pomocą przycisk o nazwie _save_and_sent_to_check. Jeżeli takich przycisków byłoby więcej, to drugi parametr mówi, który z nich użyć. Dopisujemy jeszcze dwie kolejne metody pomocnicze. Jedną wykorzystamy do testów, które zakładają, że wszystko zadziałało prawidłowo. Drugą do testów, w których oczekujemy, że system poinformuje nas o źle wypełnionym formularzu: def _add_article_ok(self, lead, tags, text, overtitle, lead_photo, gallery): response = self._add_article(lead, tags, text, overtitle, lead_photo, gallery) try: errors = response.context[ errors ] except (TypeError, KeyError): errors = [] self.assertequals(errors, [], msg=errors) self.assertequals(response.status_code, 302) response = response.follow() self.assertequals(response.status_code, 200) def _add_article_error(self, lead, tags, text, overtitle, lead_photo, gallery): response = self._add_article(lead, tags, text, overtitle, lead_photo, gallery) try: errors = response.context[ errors ] except (TypeError, KeyError): errors = []

self.assertnotequals(errors, []) self.assertequals(response.status_code, 200) Obiekt response posiada atrybut context, w którym znajduje się kontekst użyty do renderowania szablonów. Jeżeli wystąpił błąd w formularzu, to znajdzie się tam lista errors wskazująca, jakie błędy wystąpiły. Jeżeli poprawnie został dodany artykuł to oczekujemy, że errors będzie puste, a kod zwrócony przez aplikację będzie przekierowywał nas na inną stronę. Jeżeli źle wypełniliśmy formularz oczekujemy niepustej listy błędów oraz braku przekierowania. Metody self.assert sprawdzają te warunki w testach. Więcej metod jest opisanych w dokumentacji Django na temat testów 5. Jeżeli funkcja przyjmuje różne parametry, to należałoby przetestować wszystkie możliwe kombinacje tych parametrów. Niestety jest to czasochłonne, a czasami wręcz niemożliwe. Dlatego metodą przynoszącą dobre efekty jest sprawdzanie każdej pary takich parametrów. Aby to zrobić wykorzystamy dekorator generator, który na podstawie szablonu metody i przekazanych parametrów, automatycznie wygeneruje testy: @generator([ zajawka1 ], [ tag1,tag2 ], [ Tresc artykulu ], [, Nadtytul ], [ ], [ ]) def _add_article(self, lead, tags, text, overtitle, lead_photo, gallery): # poprawne dodanie artykulu self._add_article_ok(lead, tags, text, overtitle, lead_photo, gallery) @generator([, za_dluga_zajawka * 100], [ tag1,tag2 ], [ Tresc artykulu ], [ ], [ ], [ ]) def _add_article_with_wrong_lead(self, lead, tags, text, overtitle, lead_photo, gallery): # blad dodawania artykulu - niepoprawna zajawka self._add_article_error(lead, tags, text, overtitle, lead_photo, gallery) @generator([ zajawka ], [ ], [ Tresc artykulu ], [ ], [ ], [ ]) def _add_article_with_wrong_tags(self, lead, tags, text, overtitle, lead_photo, gallery): # blad dodawania artykulu - niepoprawne tagi self._add_article_error(lead, tags, text, overtitle, lead_photo, gallery) @generator([ zajawka ], [ tag1,tag2 ], [ ], [ ], [ ], [ ]) def _add_article_with_wrong_text(self, lead, tags, text, overtitle, lead_photo, gallery): # blad dodawania artykulu - niepoprawny tekst self._add_article_error(lead, tags, text, overtitle, lead_photo, gallery) @generator([ zajawka ], [ tag1,tag2 ], [ tresc ], [ za dlugi nadtytul * 100], [ ], [ ]) def _add_article_with_wrong_overtitle(self, lead, tags, text, overtitle, lead_photo, gallery): # blad dodawania artykulu - niepoprawny nadtylul self._add_article_error(lead, tags, text, overtitle, lead_photo, gallery) Kolejnymi parametrami generatora są listy zawierające możliwe wartości dla kolejnych argumentów dekorowanej funkcji. W przypadku wartości liczbowych, warto wprowadzić przynajmniej wartości ze skraju zakresu, w którym wartość jest poprawna oraz jakąś typową wartość ze środka zakresu. W przypadku łańcuchów znaków, warto sprawdzać, jak zachowuje się test przy pustych łańcuchach, bardzo długich, zawierających spacje i znaki diakrytyczne. Na koniec wyciągamy testy z naszej klasy i tworzymy z nich zestaw testów, który podpinamy do głownego zestawu testów: add_article_suite = test_loader.loadtestsfromtestcase(addarticletest) def suite(): suite = unittest.testsuite() test_suites = [ add_article_suite ] for test_suite in test_suites: suite.addtest(test_suite) return suite 5 https://docs.djangoproject.com/en/1.2/topics/testing/

Analogicznie jak w testach akcpetacyjnych należy podpiąć testy do głownego tests.py: import unittest def suite(): from cms_local.functional_tests import suite as functional_suite suite = unittest.testsuite() test_suites = [ functional_suite() ] for test_suite in test_suites: suite.addtest(test_suite) return suite Testy uruchamiamy poleceniem: python manage.py test cms_local 3.1 Testowanie javascript W MowimyJakV1 w pliku cms_local/selenium_tests.py znajduje się przykładowy test funkcjonalny sprawdzający działanie autouzupełniania tagów w formularzu tworzenia artykułu dla redaktora serwisu www.mowimyjak.pl. Opis przypadku testowego: redaktor loguje się do panelu redaktora klika na link Dodaj nowy artykuł uzupełnia pole title zaczyna uzupełnianie pola tags wybranym tagiem i czeka na podpowiedzi systemu widząc interesujący go tag potwierdza naciśnięciem klawisza ENTER następnie uzupełnia zajawkę oraz treść w edytorze TinyMCE zapisuje artykuł wylogowuje się Na początek generujemy szablon testów selenium: python manage.py gen_tests --template selenium > selenium_tests.py Szablon uzupełniamy o nowy test AddArticleTest: class AddArticleTest(AbstractMuratorTest): def setup(self): super(addarticletest, self).setup() def teardown(self): super(addarticletest, self).teardown() def test_should_show_autocomplete(self): Pod zmienną self.driver znajduje się webdriver selenium, który steruje zachowaniem przeglądarki. Uzupełniamy dwie pomocniczne metody do logowania i wylogowania:

def _login(self): self.driver.get(self.host_name + /panel-redaktora ) login_form = self.driver.find_element_by_id("login-form") login_form.find_element_by_name( username ).send_keys("redaktor1") login_form.find_element_by_name( word ).send_keys("test") login_form.submit() def _logout(self): self.driver.find_element_by_xpath("//a[contains(@href, /panel-redaktora/logout )]").click() Polecenie metoda self.driver.get powoduje uruchomienie w przeglądarce strony podanej w parametrze metodą GET. Za pomocą self.drive.find_element_by_id możemy pobrać interesujący nas element na stronie. W naszym przypadku jest to formularz. Inne dostępne metody to: find_element_by_xpath - pobiera element na stronie za pomocą wyrażenia xpath find_element_by_link_text - pobiera element na stronie na podstawie treści linku find_element_by_partial_link_text - pobiera element na stronie na podstawie fragmentu treści linku find_element_by_name - pobiera element na stronie na podstawie atrybutu name find_element_by_tag_name - pobiera element na stronie na podstawie nazwy taga find_element_by_class_name - pobiera element na stronie na podstawie nazwy klasy find_element_by_css_selector - pobiera element na stronie w sposób analogiczny do selektorów css Oprócz tego występują wszystkie wyżej wymienione metody w wersji find_elements, która powoduje pobranie listy wszystkich takich elementów, a nie tylko jednego. Dokumentacja do selenium webdriver jest dostępna w postaci pythonowych docstringów: import selenium.webdriver.remote.webdriver help(selenium.webdriver.remote.webdriver) Elementy pobrane za pomocą metod find_element same również posiadają wiele z wcześniej wymienionych metod oraz swoje unikalne. Wewnątrz pobranego formularza logowania wyszukujemy po name pola username oraz word i uzupełniamy je symulując wpisywanie tekstu za pomocą metody send_keys. Następnie na formularzu wywołujemy metodę submit, która wywoła domyślną akcję dla formularza. Jeżeli chcielibyśmy wywołać jedną z alternatywnych akcji, należałoby uruchomić submit na konkretnym elemencie np. przycisku OK lub Anuluj. Wypełniamy naszą metodę testową: def test_should_show_autocomplete(self): self._login() # potwierdz, zapytanie "Czy jestes pewien?" # czekamy, na zaladowanie sie strony # wypelniamy formularz ## wypelniamy tagi i czekamy na podpowiedz ## sprawdzamy, czy podpowiedziano tam wlasciwy tag # wypelniamy TinyMCE # zapisujemy formularz self._logout() Kliknięcie na link Dodaj nowy artykuł powoduje wyświetlenie alertu z informacją typu Czy jesteś pewien, że chcesz odstrzelić sobie palca?. Metodą self.driver.switch_to_alert przechodzimy do okienka alertu, a następnie potwierdzamy poprzez alert: self.driver.find_element_by_link_text(u"dodaj nowy artykuł").click() self.driver.switch_to_alert().accept()

Strona może ładować się dłużej lub krócej i powinniśmy zaczekać na załadowanie się jej. Załóżmy, że jak się wyświetli już właściwy tytuł, to strona jest załadowana. Do oczekiwania na pewne zdarzenie służy obiekt WebDriverWait: from selenium.webdriver.support.ui import WebDriverWait... try: # czekamy, na zaladowanie sie strony WebDriverWait(self.driver, 20).until(lambda driver : driver.title.lower().startswith(u"dodaj")) finally: Jako parametry przekazujemy webdriver oraz czas, po który zostanie wyrzucony wyjątek informujący, że nie doczekaliśmy się żądanego efektu. Następnie na naszym obiekcie, wywołujemy metodę until, której parametrem jest funkcja sprawdzająca, czy zaszło oczekiwane zdarzenie. Prototyp tej funkcji to: def func(driver): Przekazywany parametr driver to przekazany webdriver do obiektu WebDriverWait. W chwili, gdy funkcja zwróci True, kończymy oczekiwanie. W dalszej części testu wyszukujemy poszczególne pola formularza i uzupełniamy je. W przypadku tagów chcemy sprawdzić podpowiedzi. Dlatego wypełniamy tylko fragment nazwy taga i następnie oczekujemy, aż pojawi się okieno z podpowiedziami. W chwili, gdy widoczne jest okno z podpowiedziami wybieramy pierwszą automatycznie zaznaczoną odpowiedź. Aby zasymulować naciśnięcie klawisza ENTER musimy wykorzystać moduł z listą kodów klawiszy: from selenium.webdriver.common import keys W tym module znajduje się klasa Key, w której zdefiniowane są różne kody klawiszy. My wybieramy Keys.RETURN: self.driver.find_element_by_name( title ).send_keys( Artykul testowy ) self.driver.find_element_by_name( tags ).send_keys( infor ) try: def has_response(driver): ac_results = driver.find_element_by_class_name( ac_results ) return ac_results.value_of_css_property( display )!= none # czekamy, az przyjdzie odpowiedz z lista tagow WebDriverWait(self.driver, 20).until(has_response) finally: self.driver.find_element_by_name( tags ).send_keys(keys.keys.return) Na koniec sprawdzamy, czy oby na pewno uzupełniło nam o tag, który wcześniej przygotowaliśmy: value = self.driver.find_element_by_name( tags ).get_attribute( value ).strip().strip(, ) self.assertequals(value, informacja ) Zajawkę uzupełniamy w sposób analogiczny do title: self.driver.find_element_by_name( lead ).send_keys( zajawka ) Na koniec uzupełniania formularza, trzeba się trochę pogimnastykować z TinyMCE. Używając w zwykły sposób tego edytora, tak na prawdę nie uzupełniamy pola textarea, a jedynie wypełniamy iframe umieszczany na jego miejscu. Aby przejść do odpowiedniego iframe należy wykorzystać metodę switch_to_frame i jako parametr podać nazwę iframe:

# TinyMCE dziala w iframe self.driver.switch_to_frame( id_text_ifr ) Dalej już pobieramy element body wewnątrze tego iframe, które w przypadku TinyMCE jest zasze tinymce i klikamy na paragraf, czyli symulujemy dokładnie to co byśmy robili kursorem: tinymce_frame = self.driver.find_element_by_id( tinymce ) textarea = tinymce_frame.find_element_by_xpath( p ) textarea.click() Dalej pozostaje już tylko zasymulować wpisywanie tekstu i przełączyć się z iframe do głownego okna: textarea.send_keys( tresc ) self.driver.switch_to_default_content() Na koniec zapisujemy i wylogowujemy się: self.driver.find_element_by_name( _save ).submit() self._logout() Teraz należy uruchomić testy. Testy selenium działają poprzez instruowanie rzeczywistej przeglądarki do wykonywania poleceń, jakie wykonywałby człowiek. Z tego powodu, musimy mieć uruchomiony serwer z działającą aplikacją oraz odpalić oddzielnie testy. Inaczej, niż to było w przypadku testów akceptacyjnych, które w całości odbywały się po stronie pythona. Do uruchomienia serwera z aplikacją wykorzystamy serwer testowy django: python manage.py testserver cms_local/fixtures/all.json W pliku cms_local/fixtures/all.json znajdują się fixtures wykorzystywane w tym teście. W przypadku MowimyJakV1 ten plik jest generowany poprzez uruchomienie merge_fixtures.py. Można by te wszystkie fixtures podać po kolei jako parametry do testserver, ale z lenistwa wolę generować jeden plik... Następnie należy uruchomić nasze testy. Testy selenium uruchamiamy za pomocą polecenia: python manage.py testselenium cms_local To polecenie zakłada, że testy selenium umieszczone są w pliku selenium_tests.py w katalogu podanej aplikacji. Domyślnie uruchamiana jest przeglądarka Firefox. Jeżeli chcecie wykorzystać inną ustawcie w pliku local_settings.py zmienną SELENIUM_WEBDRIVER na wartość Firefox, Ie lub Chrome. Jeżeli testserver działa pod adresem innym niż http://127.0.0.1:8000, to należy adres tego serwera przekazać do testselenium za pomocą parametry testserver. 4 Test driven development W podejściu TDD w pierwszej kolejności pisane są testy, następnie kod. Takie podejście powoduje, że więcej pracy jest na początku tworzenia projektu, ale nie ma lawiny rzeczy do poprawy bliżej końca projektu. Tworzenie według TDD działa według schematu: 1. Weź przypadek użycia. 2. Utwórz przypadek testowy na podstawie przypadku użycia. 3. Utwórz test na podstawie przypadku testowego. 4. Uruchom test. Test wyszedł niepomyślnie? To dobrze. 5. Dopisz fragment testowanej funkcjonalności. 6. Uruchom test.

jeżeli test przeszedł pomyślnie, funkcjonalność jest skończona; przejdź do punktu 1 jeżeli test nie powiódł się, przejdź do punktu 5 Dzięki TDD czas nie jest tracony na przygotowanie funkcjonalności, która może się nam przydać. Robione jest tylko to, co niezbędne. W przypadku późniejszych zmian, testy zapewniają, że nie wprowadzimy błędów w istniejącej funkcjonalności. Kluczem do sprawnego stosowania TDD jest dobry opis przypadków użycia. Przypadek użycia musi zawierać przynajmniej: aktorów biorących udział warunki początkowe warunki końcowe przebieg zdarzeń Przykładowy przypadek użycia rejestracja użytkownika : aktorzy niezalogowany użytkownik warunki poczatkowe brak konta w serwisie warunki końcowe: utworzone aktywne konto w serwisie konto jest przypisane do grupy użytkownicy użytkownik jest zalogowany przebieg zdarzeń: niezarejestrowany użytkownik wchodzi na stronę rejestracji użytkownik wypełnia obowiazkowe pola: nazwa użytkownika hasło powtórz hasło adres email akceptuję regulamin użytkownik klika zarejestruj system przekierowuje użytkownika na SG i wyświetla komunikat z informacją, aby użytkownik aktywował link w mailu wysłanym na jego skrzynkę użytkownik wchodzi na skrzynkę, otwiera maila i klika link aktywacyjny system aktywuje konto użytkownika i wyświetla informację o poprawnej aktywacji alternatywny przebieg zdarzeń: użytkownik wypełnia również niektóre pola nieobowiązkowe Przypadek testowy to realizacja przypadku użycia widziana już z perspektywy wyłacznie strony testującej. Pomijamy elementy, które musi wykonać system, ale sprawdzamy, czy interesujące nas efekty zaszły. Na tym etapie wiemy

już lub przewidujemy jak będą nazywać się poszczególne pola w formularzach, więc wykorzystujemy tę wiedzę przy opisywaniu przebiegu zdarzeń. Przykładowy przypadek testowy: aktorzy niezalogowany użytkownik warunki poczatkowe czysty system bez użytkowników, ale z danymi umożliwiającymi uruchomienie go, np. predefiniowane grupy użytkowników warunki końcowe utworzone aktywne konto użytkownika utworzony profil użytkownika konto przypisane do grupy użytkownicy wysłany 1 mail aktywacyjny użytkownik jest zalogowany przebieg zdarzeń niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept użytkownik klika przycisk zarejestruj użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacją o konieczności aktywacji z maila użytkownik klika na link z maila użytkownik otrzymuje informację, że jego konto jest zarejestrowane alternatywne przebiegi zdarzeń: użytkownik błędnie wypełnia pola obowiązkowe - otrzymuje komunikat o błędzie użytkownik wypełnia pola nieobowiązkowe - na zakończenie powinny być ustawione te pola Następnie przechodzimy do tworzenia kodu samego testu. Tworzymy szablon testów akceptacyjnych: python manage.py gen_tests --template acceptance > acceptance_tests.py Ponieważ jeszcze nie mamy danych, nie wypełniamy fixtures. Jak już będą istniały zręby projektu z wypełnionymi podstawowymi kategoriami itp. będzie można wykorzystać te dane do inicjalizacji testów: class AbstractMuratorTest(AcceptanceTestCase): fixtures = [ ] Dla każdej funkcjonalności tworzymy oddzielną klase testów: class RegistrationTest(AbstractMuratorTest): def setup(self): super(registrationtest, self).setup() def teardown(self): super(registrationtest, self).teardown()

def test_register_basic(self): Warunki początkowe przypadku testowego definiują nam środowisko, w jakim uruchamiany musi być test. To środowisko przygotowujemy w metodzie setup. W przykładowym teście wymagane jest, aby istniała już grupa użytkownicy : def setup(self): super(registrationtest, self).setup() self.group = Group.objects.create(name=u użytkownicy ) self.user_name = testuser self.user_word = testword self.user_email = test@example.com W części teardown sprzątamy wszystko po sobie. Testy w django uruchamiane są wewnątrz jednej transakcji, która jest wycofywana na zakończenie testów i bazwa wraca do stanu pierwotnego. Dlatego można pominąć czyszczenie utworzonych obiektów w bazie. Jednak jeżeli w trakcie testu wykorzystywane były jakieś inne moduły aplikacji (np. zapis plików na dysku, cache), to trzeba je przywrócić do pierwotnego statu własnie w metodzie teardown. W klasie testu powinien znaleźć się przynajmniej jeden test, odpowiadający głownemu przebiegowi zdarzeń oraz testy odpowiadające alternatywnym przebiegom. Zacznijmy od wypełnienia testu opisem przebiegu zdarzeń.: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept # użytkownik klika przycisk "zarejestruj" # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj # użytkownik klika na link z maila # użytkownik otrzymuje informację, że jego konto jest zarejestrowane # sprawdzenie warunków końcowych Teraz zamieniamy poszczególne kroki przebiegu zdarzeń na symulowane czynności wykonywane przez użytkownika. W pierwszej kolejności musimy wejść na stronę rejestracji.: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ response = self.app.get( /rejestracja/ ) # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept # użytkownik klika przycisk "zarejestruj" # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj # użytkownik klika na link z maila # użytkownik otrzymuje informację, że jego konto jest zarejestrowane # sprawdzenie warunków końcowych Pod atrybutem app znajduje się testowana aplikacja. Wywołując na niej metodę get( /rejestracja/ ) symulujemy zapytanie, jakie wykonałaby przeglądarka użytkownika. W wyniku otrzymujemy obiekt response. Jeżeli na stronie rejestracyjnej znajduje się jeden formularz to znajdzie się on pod atrybutem form obiektu response. Wszystkie formularze dostępne są w słowniku forms. Formularz zachowuje się podobnie do słownika. Możemy wypełnić interesujące nas pola podpisując odpowiednie wartości w formularzu. Zgodnie z opisem musimy wypełnić pola username, word, reword (czyli ponownie wpisane hasło), email i zaakceptować regulamin ( reg_accept ).: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ response = self.app.get( /rejestracja/ ) # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept form = response.form form[ username ] = self.user_name

form[ word ] = self.user_word form[ reword ] = self.user_word form[ email ] = self.user_email form[ reg_accept ] = True # użytkownik klika przycisk "zarejestruj" # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj # użytkownik klika na link z maila # użytkownik otrzymuje informację, że jego konto jest zarejestrowane # sprawdzenie warunków końcowych Użytkownik klika przycisk zarejestruj. Wysłanie formularza symulujemy metodą submit formularz. Metoda wywołana bez parametrów wykona domyślną akcję formularza. Jeżeli podamy parametry, to pierwszy z nich jest wartością name przycisku, a drugi jest indeksem takich przycisków (w przypadku, gdyby było więcej przycisków z takim samym name).: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ response = self.app.get( /rejestracja/ ) # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept form = response.form form[ username ] = self.user_name form[ word ] = self.user_word form[ reword ] = self.user_word form[ email ] = self.user_email form[ reg_accept ] = True # użytkownik klika przycisk "zarejestruj" response = form.submit( zarejestruj, 0) # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj # użytkownik klika na link z maila # użytkownik otrzymuje informację, że jego konto jest zarejestrowane # sprawdzenie warunków końcowych Po poprawnej rejestracji powinniśmy być przekierowani na SG i otrzymać komunikat z informacją Na adres XYZ został wysłany link aktywacyjny.. Aby podążać za przekierowaniem należy wywołać metodę follow na obiekcie response. Do sprawdzenia, czy na stronie znajduje się oczekiwany przez nas tekst możemy użyć atrybutu content.: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ response = self.app.get( /rejestracja/ ) # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept form = response.form form[ username ] = self.user_name form[ word ] = self.user_word form[ reword ] = self.user_word form[ email ] = self.user_email form[ reg_accept ] = True # użytkownik klika przycisk "zarejestruj" response = form.submit( zarejestruj, 0) # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj response = response.follow() self.assertequals(response.status_code, 200) self.asserttrue((u Na adres %s został wysłany link aktywacyjny % self.user_email) in response.co # użytkownik klika na link z maila # użytkownik otrzymuje informację, że jego konto jest zarejestrowane # sprawdzenie warunków końcowych

W trakcie działania testów Django zamiast wysyłać maile umieszcza je w testowej skrzynce django.core.mail.outbox. Testowa skrzyna jest zwykła listą wysłanych maili zawierającą obiekty EmailMessage. W naszym teście sprawdzamy, czy został wysłany jeden mail, na podany przez nas adres i czy zawiera link rejestracyjny.: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ response = self.app.get( /rejestracja/ ) # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept form = response.form form[ username ] = self.user_name form[ word ] = self.user_word form[ reword ] = self.user_word form[ email ] = self.user_email form[ reg_accept ] = True # użytkownik klika przycisk "zarejestruj" response = form.submit( zarejestruj, 0) # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj response = response.follow() self.assertequals(response.status_code, 200) self.asserttrue((u Na adres %s został wysłany link aktywacyjny % self.user_email) in response.co # użytkownik klika na link z maila from django.core import mail self.assertequals(len(mail.outbox), 1) activation_mail = mail.outbox[0] self.asserttrue(activation_mail.subject.startswith(u Aktywuj konto )) self.asserttrue(self.user_email in activation_mail.to) import re match = re.search( (?P<link>/aktywacja/\w+/), activation_mail.body) self.asserttrue(match is not None) link = match.groupdict()[ link ] response = self.app.get(link) # użytkownik otrzymuje informację, że jego konto jest zarejestrowane self.asserttrue((u Twoje konto zostało zarejestrowane ) in response.content) # sprawdzenie warunków końcowych Na sam koniec sprawdzamy rzecz najważniejszą - czy mamy utworzone konto i jest ono poprawne.: def test_register_basic(self): # niezarejestrowany użytkownik wchodzi na stronę rejestracji pod adresem /rejestracja/ response = self.app.get( /rejestracja/ ) # użytkownik wypełnia pola obowiązkowe username, word, reword, email, reg_accept form = response.form form[ username ] = self.user_name form[ word ] = self.user_word form[ reword ] = self.user_word form[ email ] = self.user_email form[ reg_accept ] = True # użytkownik klika przycisk "zarejestruj" response = form.submit( zarejestruj, 0) # użytkownik trafia na SG i otrzymuje komunikat, z informacją o poprawnej rejestracji i informacj response = response.follow() self.assertequals(response.status_code, 200) self.asserttrue((u Na adres %s został wysłany link aktywacyjny % self.user_email) in response.co # użytkownik klika na link z maila from django.core import mail self.assertequals(len(mail.outbox), 1) activation_mail = mail.outbox[0] self.asserttrue(activation_mail.subject.startswith(u Aktywuj konto ))

self.asserttrue(self.user_email in activation_mail.to) import re match = re.search( (?P<link>/aktywacja/\w+/), activation_mail.body) self.asserttrue(match is not None) link = match.groupdict()[ link ] response = self.app.get(link) # użytkownik otrzymuje informację, że jego konto jest zarejestrowane self.asserttrue((u Twoje konto zostało zarejestrowane ) in response.content) # sprawdzenie warunków końcowych # czy konto istnieje i jest poprawne? try: user = User.objects.get(username=self.user_name) except User.DoesNotExist: user = None self.asserttrue(user is not None) self.assertequals(user.username, self.user_name) self.assertequals(user.email, self.user_email) # czy użytkownik jest przypisany do grupy użytkowników? self.asserttrue(self.group in user.groups.all()) # czy profil istnieje? try: profile = user.profile except Profile.DoesNotExist: profile = None self.asserttrue(profile is not None) # czy użytkownik jest zalogowany? self.asserttrue((u /logout ) in response.content) Po zakończeniu pisania testu podłączamy go do testów aplikacji w pliku tests.py.: import unittest def suite(): from cms_local.acceptance_tests import suite as acceptance_suite suite = unittest.testsuite() test_suites = [ acceptance_suite() ] for test_suite in test_suites: suite.addtest(test_suite) return suite Następnie uruchamiamy testy.: python manage.py test -v 2 <aplikacja> Testy powinny dać w rezultacie fail lub error. Jeżeli któryś test przeszedł poprawnie, a nie mamy napisanego dla niego kodu, który miałby go wykonywać, to znaczy, że ten test nic nie sprawdza. W następnej kolejności tworzymy kod, który wykonuje opisaną funkcjonalność. W chwili, gdy test przechodzi poprawnie, funkcjonalność jest zakończona (albo test jest niekompletny).