layout | title | inheader | permalink |
---|---|---|---|
page |
Viikko 3 |
false |
/tehtavat3/ |
{% include paivitys_kesken.md %}
{% include miniproj_ilmo.md %}
{% include laskari_info.md part=3 %}
Tehtävät liittyvät storyjen hyväksymistestauksen automatisointiin tarkoitetun Robot Frameworkin, sekä selainsovellusten testaamiseen käytettävän Selenium-kirjaston soveltamiseen.
{% include typo_instructions.md %}
{% include norppa.md %}
{% include poetry_ongelma.md %}
Tehtävät palautetaan GitHubiin, sekä merkitsemällä tehdyt tehtävät palautussovellukseen <{{site.stats_url}}> välilehdelle "my submission".
Kaikki tämän viikon tehtävät palautetaan jo edellisillä viikoilla käyttämääsi palautusrepositorioon, sinne tehtävän hakemiston viikko3 sisälle.
Katso tarkempi ohje palautusrepositoriota koskien täältä.
Lue täällä oleva Robot Framework -johdanto ja tee siihen liittyvät tehtävät.
Hae kurssirepositorion hakemistossa viikko3/login-robot oleva projekti.
- Kopioi projekti palatusrepositorioosi, hakemiston viikko3 sisälle.
Tutustu ohjelman rakenteeseen. Sovellus noudattaa ns. kerrosarkkitehtuuria eli se on rakenteeltaan samanlainen kuin kurssin Ohjelmistotekniikka referenssisovellus. Sovelluksen käyttöliittymä on toteutettu luokkaan App
, ja sovelluslogiikka luokkaan UserService
.
Eräs huomionarvoinen seikka on se, että UserService
-olio ei tallenna suoraan User
-oliota vaan epäsuorasti UserRepository
-luokan olion kautta. Mistä on kysymys?
Sovelluksen käyttämään tietoon kohdistuvien operaatioiden abstrahointiin sovelluslogiikasta löytyy useita suunnittelumalleja, kuten Data Access Object, Active Record ja Repository. Kaikkien näiden suunnittelumallien perimmäinen idea on siinä, että sovelluslogiikalta tulee piilottaa tietoon kohdistuvien operaatioiden yksityiskohdat.
Esimerkiksi repositorio-suunnittelumallissa tämä tarkoittaa sitä, että tietokohteeseen kohdistetaan operaatioita erilaisten funktioiden tai metodien kautta, kuten find_all
, create
ja delete
. Tämän abstraktion avulla sovelluslogiikka ei ole tietoinen operaatioiden yksityiskohdista, jolloin esimerkiksi tallennustapaa voidaan helposti muuttaa.
Sovellukseen on määritelty repositorio-suunnittelumallin mukainen luokka UserRepository
. Luokka tallentaa sovelluksen käyttäjiä muistiin. Jos päättäisimme tallentaa käyttäjät esimerkiksi SQLite-tietokantaan, ei tämä vaatisi muutoksia luokan ulkopuolelle.
Asenna projektin riippuvuudet ja kokeile suorittaa index.py
-tiedosto. Ohjelman tuntemat komennot ovat login ja new.
Suorita myös projektiin siihen liittyvät Robot Framework -testit virtuaaliympäristössä komennolla robot src/tests
.
Tutki miten Robot Framework -testit on toteutettu hakemistossa src/tests. Tutki myös, miten avainsanat on määritelty src-hakemiston AppLibrary.py-tiedoston AppLibrary
-luokassa. Huomioi erityisesti, miten testit käyttävät testaamisen mahdollistavaa StubIO
-oliota käyttäjän syötteen ja ohjelman tulosteen käsittelyyn. Periaate on täsmälleen sama kuin viikon 1 tehtävien riippuvuuksien injektointiin liittyvässä esimerkissä.
Saatat löytää .robot-tiedostoista ennestään tuntemattomia ominaisuuksia. resource.robot-tiedossa on määritelty avainsana Input Credentials
, jolla on argumentit username
ja password
:
Input Credentials
[Arguments] ${username} ${password}
Input ${username}
Input ${password}
Run Application
Kyseinen avainsana on käytössä login.robot-tiedostossa seuraavasti:
Input Credentials kalle kalle123
Lisäksi login.robot-tiedoston *** Settings ***
-osiossa on uusi asetus, Test Setup
. Kyseisen asetuksen avulla voimme määritellä avainsanan, joka suoritetaan ennen jokaista testitapausta. Tässä tapauksessa ennen jokaista testiä halutaan suorittaa avainsana Create User And Input Login Command
, joka luo uuden käyttäjän ja antaa sovellukselle login-komennon.
Toteuta user storylle User can log in with valid username/password-combination seuraavat testitapaukset login.robot-tiedostoon:
*** Test Cases ***
Login With Incorrect Password
# ...
Login With Nonexistent Username
# ...
Suorita testitapauksissa sopivat avainsanat, jotta haluttu tapaus tulee testattua.
Lisää testihakemistoon uusi testitiedosto register.robot. Toteuta tiedostoon user storylle A new user account can be created if a proper unused username and a proper password are given seuraavat testitapaukset:
*** Test Cases ***
Register With Valid Username And Password
# ...
Register With Already Taken Username And Valid Password
# ...
Register With Too Short Username And Valid Password
# ...
Register With Enough Long But Invald Username And Valid Password
# ...
Register With Valid Username And Too Short Password
# ...
Register With Valid Username And Long Enough Password Containing Only Letters
# ...
- Käyttäjätunnuksen on oltava merkeistä a-z koostuva vähintään 3 merkin pituinen merkkijono, joka ei ole vielä käytössä. Vinkki: säännölliset lausekkeet ja ^[a-z]+$.
- Salasanan on oltava pituudeltaan vähintään 8 merkkiä ja se ei saa koostua pelkästään kirjaimista.
- Säännöllisten lausekkeiden kokeilu ja testaaminen onnistuu hyvin esim. seuraavassa palvelussa https://rubular.com/
Säännöllisissä lausekkeissa voi hyödyntää Pythonin re-moduulia seuraavasti:
import re
if re.match("^[a-z]+$", "kalle"):
print("Ok")
else:
print("Virheellinen")
Tee testitapauksista suoritettavia ja täydennä ohjelmaa siten että testit menevät läpi. Oikea paikka koodiin tuleville muutoksille on src/services/user_service.py-tiedoston UserService
-luokan metodi validate
.
Vinkki 2: et välttämättä tarvitse säännöllisiä lausekkeita mihinkään...
HUOM 1: Testitapaukset kannattaa toteuttaa yksi kerrallaan, laittaen samalla vastaava ominaisuus ohjelmasta kuntoon. Eli ÄLÄ copypastea ylläolevaa kerrallaan tiedostoon, vaan etene pienin askelin. Jos yksi testitapaus ei mene läpi, älä aloita uuden tekemistä ennen kuin kaikki ongelmat on selvitetty. Seuraava luku antaa muutaman vihjeen testien debuggaamiseen.
HUOM 2: Saattaa olla hyödyllistä toteuttaa resource.robot-tiedostoon avainsana Input New Command
ja register.robot-tiedostoon avainsana Input New Command And Create User
, joka antaa sovellukselle new-komennon ja luo käyttäjän testejä varten. Avainsana kannattaa suorittaa ennen jokaista testitapausta hyödyntämällä Test Setup
-asetusta.
On todennäköistä että testien tekemisen aikana tulee ongelmia, joiden selvittäminen ei ole triviaalia. Epäonnistuneen testitapauksen kohdalla kannattaa miettiä mahdollisia syitä:
- Onko vika testissä, eli toimiiko sovellus kuten pitääkin? Voit esimerkiksi testata sovelluksen toimivuuden manuaalisesti. Jos näin on, keskity testin korjaamiseen
- Onko vika sovelluksessa, eli eikö manuaalisesti testattu sovellus toimi kuten pitäisi? Jos näin on, keskity tarkastelemaan ohjelman suoritusta epäonnistuneessa testitapauksessa
Tutustutaan seuraavaksi tekniikoihin, jotka helpottavat ja nopeuttavat virheiden metsästystä.
Kun kohtaat epäonnistuvan testitapauksen, kannattaa testien suorittamista nopeuttaa suorittamalla vain epäonnistunut testitapaus. Jos testitapaus Login With Correct Credentials
, voimme suorittaa ainoastaan sen seuraavalla komennolla:
robot -t "Login With Correct Credentials" src/tests/login.robot
Komennolle robot
annetaan siis -t
-optionin kautta suoritettavan testitapauksen nimi ja tiedosto, jossa testitapaus sijaitsee.
Jos virheen löytäminen pelkän manuaalisen testauksen avulla ei tuota tulosta, kannattaa alkaa tutkimaan miten ohjelman suoritus etenee. Ensin on jollain tavalla rajattava, missä ongelma saattaisi olla. Jos esimerkiksi Login With Correct Credentials
-testitapaus epäonnistuu, on ongelma luultavasti UserService
-luokan metodissa check_credentials
. Voimme pysäyttää ohjelman suorituksen halutulle riville hyödyntämällä pdb-moduulia:
# ...
# debugattavaan tiedostoon tulee tuoda tarvittavat moduulit
import sys, pdb
class UserService:
def __init__(self, user_repository):
self._user_repository = user_repository
def check_credentials(self, username, password):
# pysäytetään ohjelman suoritus tälle riville
pdb.Pdb(stdout=sys.__stdout__).set_trace()
if not username or not password:
raise UserInputError("Username and password are required")
user = self._user_repository.find_by_username(username)
if not user or user.password != password:
raise AuthenticationError("Invalid username or password")
return user
# ...
Ohjelman suorituksen pysäyttäminen onnistuu siis kutsumalla Pdb
-luokan metodia set_trace
. Jotta tulosteet tulisivat näkyviin testien suorituksen aikana, tulee luokan konstruktorin stdout
argumentin arvoksi asettaa sys.__stdout__
. Tätä varten debugattavaan tiedostoon tulee tuoda pdb
-moduulin lisäksi sys
-moduuli, joka tapahtuu esimerkissä import sys, pdb
-rivillä.
Käynnistä nyt ohjelma uudelleen, jotta muutokset koodiin astuvat voimaan. Suorita sen jälkeen pelkästään Login With Correct Credentials
-testitapaus edellä mainitun ohjeen mukaisesti. Kun testitapauksen suoritus saavuttaa check_credentials
-metodin kutsun, koodin suoritus pysähtyy ja palvelinta suorittavalle komentoriville ilmestyy seuraavanlainen komentorivi:
-> if not username or not password:
(Pdb)
Kyseessä on interaktiivinen komentorivi, jossa voimme suorittaa koodia. Nuoli (->
) viittaa seuraavaksi suoritettavaan koodiriivin. Katsotaan komentorivin avulla, mitkä ovat muuttujien username
ja password
arvot:
(Pdb) username
'kalle'
(Pdb) password
'kalle123'
(Pdb)
Annamme siis komentoriville syötteen ja painamme Enter-painiketta. Jatketaan koodin suorittamista antamalla syöte next()
. Koodi on ohittanut if
-lauseen (koska muuttujilla oli arvot) ja on seuraavaksi suorittamassa riviä user = self._user_repository.find_by_username(username)
:
-> user = self._user_repository.find_by_username(username)
(Pdb)
Suoritetaan rivi syöttämällä uudestaan next()
ja tulostetaan user
-muuttujan arvo:
-> if not user or user.password != password:
(Pdb) user
<entities.user.User object at 0x10f7a55e0>
Kun olet lopettanut debuggaamiseen, syötä exit()
ja poista koodista set_trace
-metodin kutsu.
Tarkastellaan edellisestä tehtävästä tutun toiminnallisuuden tarjoamaa esimerkkiprojektia, joka löytyy kurssirepositorion hakemistossa viikko3/web-login-robot oleva projekti. Sovellus on toteutettu Flask-nimisellä minimalistisella web-sovelluskehyksellä.
Hae projekti ja kopioi se palatusrepositorioosi, hakemiston viikko3 sisälle.
Asenna projektin riippuvuudet komennolla poetry install
ja käynnistä se virtuaaliympäristössä komennolla python3 src/index.py
. Sovelluksen käynnistymisen jälkeet pääset käyttämään sitä avaamalla selaimella osoitteen http://localhost:5000
HUOM: macOS Monterey käyttöjärjestelmällä Chrome saattaa antaa virheilmoituksen 403. Tällöin muokkaa tiedoston index.py
sisältö seuraavanlaiseksi
from app import app
if __name__ == "__main__":
app.run(port=8000, debug=True)
Tämän lisäksi muuta tiedostossa tests/resource.robot
muuttujan ${SERVER}
arvoksi localhost:8000
.
![]({{ "/images/py-lh3-2.png" | absolute_url }}){:height="200px" }
Sovellus siis toimii localhostilla eli paikallisella koneellasi portissa 5000.
Sovelluksen rakenne on suunnilleen sama kuin tehtävien 4-5 ohjelmassa. Poikkeuksen muodostaa pääohjelma, joka sisältää selaimen tekemät HTTP-pyynnöt. Tässä vaiheessa ei ole tarpeen tuntea HTTP-pyyntöjä käsittelevää koodia kovin tarkasti. Katsotaan kuitenkin pintapuolisesti mistä on kysymys.
Polulle "/" eli sovelluksen juureen, osoitteeseen http://localhost:5000 tulevat pyynnöt käsittelee mainista seuraava koodinpätkä:
@app.route("/")
def render_home():
return render_template("index.html")
Koodi muodostaa Jinja-kirjaston avulla src/templates/index.html-tiedostosta löytyvästä sivupohjasta HTML-muotoisen sivun ja palauttaa sen käyttäjän selaimelle.
Sivupohja näyttää seuraavalta:
{% raw %}{% extends "layout.html" %} {% block title %} Ohtu Application {%
endblock %} {% block body %}
<h1>Ohtu Application</h1>
<ul>
<li><a href="/login">Login</a></li>
<li><a href="/register">Register new user</a></li>
</ul>
{% endblock %}{% endraw %}
Kaikki GET-alkuiset määrittelyt ovat samanlaisia, ne ainoastaan muodostavat HTML-sivun (joiden sisällön määrittelevät sivupohjat sijaitsevat hakemistossa src/templates) ja palauttavat sivun selaimelle.
POST-alkuiset määrittelyt ovat monimutkaisempia, ne käsittelevät lomakkeiden avulla lähetettyä tietoa. Esimerkiksi käyttäjän kirjautumisyrityksen käsittelee seuraava koodi:
@app.route("/login", methods=["POST"])
def handle_login():
username = request.form.get("username")
password = request.form.get("password")
try:
user_service.check_credentials(username, password)
return redirect_to_ohtu()
except Exception as error:
flash(str(error))
return redirect_to_login()
Koodi pääsee käsiksi käyttäjän lomakkeen avulla lähettämiin tietoihin request-olion kautta:
username = request.form.get("username")
password = request.form.get("password")
Koodi tarkistaa käyttäjätunnuksen ja salasanan oikeellisuuden kutsumalla UserService
-luokan metodia check_credentials
. Jos kirjautuminen onnistuu, ohjataan käyttäjä "/ohtu"-polun sivulle. Jos se epäonnistuu, check_credentials
-metodi nostaa virheen, jonka käsittelemme except
-lohkossa ohjaamalla käyttäjän "/login"-polun sivulle ja näyttämällä siellä virheilmoituksena virheen sisältämän viestin.
Tutustu nyt sovelluksen rakenteeseen ja toiminnallisuuteen. Saat sammutettua sovelluksen painamalla komentoriviltä ctrl+c
tai ctrl+d
.
Jatketaan saman sovelluksen parissa.
Käynnistä web-sovellus edellisen tehtävän tapaan komentoriviltä. Varmista selaimella, että sovellus on päällä.
Selenium WebDriver -kirjaston avulla on mahdollista simuloida selaimen käyttöä koodista käsin. Seleniumin käyttö Robot Framework -testeissä onnistuu valmiin, SeleniumLibrary-kirjaston avulla.
Jotta selainta käyttävien testien suorittamien on mahdollista, täytyy lisäksi asentaa halutun selaimen ajuri. Projektin testit käyttävät Chrome- tai Chromium-selainta, jolla testejä voi suorittaa käyttämällä ChromeDriver-ajuria. Ennen kuin siirrymme testien pariin, asenna ChromeDriver seuraamalla tätä ohjetta.
Kun Chrome-ajuri on asennettu onnistuneesti, avaa uusi terminaali-ikkuna ja suorita projektin testit virtuaaliympäristössä komennolla robot src/tests
. Huomaa, että web-sovelluksen tulee olla käynnissä toisessa terminaali-ikkunassa. Komennon pitäisi suorittaa onnistuneesti kaksi testitapausta, Login With Correct Credentials
ja Login With Incorrect Password
. Testitapausten suoritusta voi seurata aukeavasta Chrome-selaimen ikkunasta.
HUOM: Windows 10 / WSL2 -käyttäjänä saatat törmätä seuraavaan virheilmoitukseen:
Suite setup failed:
WebDriverException: Message: unknown error: Chrome failed to start: crashed.
(unknown error: DevToolsActivePort file doesn't exist)
(The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)
Tämä ohje saattaa tuoda ratkaisun.
Yksi lisävaihtoehto WSL käyttäjille on ajaa Web-sovelluksemme serveriä poetryssa WSL:n puolella, ja ajaa selenium/robot-testit poetryssa Windowsin PowerShellin puolella:
- Asenna Python windowsille jos se ei ole jo asennettu
- Asenna poetry windowsille ajamalla PowerShellissä
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
- Lisää asennuksen päätteksi kerrottu polku esim.
C:\Users\<user>\AppData\Roaming\Python\Scripts
järjestelmän PATH-muuttujaan äskeisessä ChromeDriver-ohjeessa kerrotulla tavalla - Kloonaa projekti windowsin tiedostojärjestelmän puolelle (löytyy WSL-järjestelmästä /mnt hakemiston alta aivan tiedostojärjestelmän juuresta) esim. työpöydälle
/mnt/c/Users/<user>/Desktop
- Asenna riippuvuudet tavallisesti poetryssa ajamalla
poetry install
juuri kloonatun web-login-robot-hakemiston juuressa - Asenna ChromeDriver windowsille äskeisen ohjeen mukaan
- Aja Selenium/Robot testit web-login-robot-hakemiston juuresta komennolla
poetry run robot .\src\tests\
HUOM2: seuraava virheilmoitus kertoo siitä, että suoritat testejä ilman että sovellus on päällä:
[ ERROR ] Error in file '/.../viikko3/web-login-robot/src/tests/resource.robot'
on line 3: Initializing library 'AppLibrary' with no arguments failed:
ConnectionError: HTTPConnectionPool(host='localhost', port=5000):
Max retries exceeded with url: /tests/reset (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f459e7c4280>:
Failed to establish a new connection: [Errno 111] Connection refused'))
Testit siis olettavat, että sovellus on käynnissä. Käynnistä siis sovellus yhteen terminaaliin, avaa uusi ja suorita testit siellä.
Tutustutaan aluksi testitapauksien yhteisiin asetuksiin ja avainsanoihin, jotka löytyvät src/tests/resource.robot-tiedostosta. Tiedoston sisältö on seuraava:
*** Settings ***
Library SeleniumLibrary
Library ../AppLibrary.py
*** Variables ***
${SERVER} localhost:5000
${BROWSER} chrome
${DELAY} 0.5 seconds
${HOME URL} http://${SERVER}
${LOGIN URL} http://${SERVER}/login
${REGISTER URL} http://${SERVER}/register
*** Keywords ***
Open And Configure Browser
Open Browser browser=${BROWSER}
Maximize Browser Window
Set Selenium Speed ${DELAY}
Login Page Should Be Open
Title Should Be Login
Main Page Should Be Open
Title Should Be Ohtu Application main page
Go To Login Page
Go To ${LOGIN URL}
*** Settings ***
osiossa on käytössä projektin oma AppLibrary.py
-kirjasto sekä edellä mainittu SeleniumLibrary-kirjasto. SeleniumLibrary-kirjasto tuo mukaan lukuisia uusia avainsanoja, joista kaikki on dokumentoitu täällä.
Tiedostossa on myös ennestään tuntematon *** Variables ***
-osio. Kuten osion nimi kertoo, voimme määritellä osion sisällä muuttujia, jotka ovat kaikkien avainsanojen käytössä. Huomaa, että osion alla määritellyt muuttujat kirjoitetaan isoilla kirjaimilla, toisin kuin argumentit. Muuttujia kannattaa suosia aina kovakoodattujen arvojen sijaan.
*** Keywords ***
-osiossa on määritelty yleiskäyttöisiä avainsanoja:
Open And Configure Browser
-avainsana käynnistää selaimen käyttämällä SeleniumLibrary-kirjaston Open Browser -avainsanaa antaenbrowser
-argumentin arvoksiBROWSER
-muuttujan arvon, joka onchrome
. Lisäksi avainsana asettaa viiveeksi Selenium-komentojen välilleDELAY
-muuttujan arvon käyttämällä Set Selenium Speed -avainsanaa. Pidempi viive helpottaa testien suorituksen seuraamista. Maximize Browser Window toimii kuten olettaa saattaa, itse poistin komennon sillä en tykkää että Robotin avaama selainikkuna peittää koko ruudun. Ikkunan koon voi asettaa haluamakseen avainsanalla Set Window SizeLogin Page Should Be Open
- jaMain Page Should Be Open
-avainsanojen tarkoitus on tarkistaa, että käyttäjä on oikealla sivulla. Ne käyttävät Title Should Be -avainsanaa, joka tarkistaa HTML-sivun title-elementin arvon. Title-elementin arvon sijaan voisimme esimerkiksi tarkistaa, että sivulta löytyy tietty teksti käyttämällä Page Should Contain -avainsanaaGo To Login Page
-avainsana käyttää Go To -avainsanaa avatakseen selaimessa kirjautumis-sivun, jonka URL on tallennettuLOGIN URL
-muuttujaan
Tutustutaan seuraavaksi itse testitapauksiin avaamalla tiedosto src/tests/login.robot. Tiedoston *** Settings ***
-osio on seuraava:
*** Settings ***
Resource resource.robot
Suite Setup Open And Configure Browser
Suite Teardown Close Browser
Test Setup Create User And Go To Login Page
Osiossa on käytössä ennestään tuntemattomat Suite Setup
-, Suite Teardown
- ja Test Setup
-asetukset. Niiden merkitykset ovat seuraavat:
Suite Setup
-asetuksen avulla voimme suorittaa avainsanan ennen tiedoston ensimmäistä testitapausta (Test Setup
sen sijaan suoritetaan ennen jokaista testitapausta)Suite Teardown
-asetuksen avulla voimme suorittaa avainsanan tiedoston viimeisen testitapauksen jälkeen (Test Teardown
sen sijaan suoritetaan jokaisen testitapauksen jälkeen)
Tiedoston *** Keywords ***
osiossa on testitapausten käyttämiä avainsanoja:
Login Should Succeed
-avainsana tarkastaa, että käyttäjä on siirtynyt oikealla sivulle onnistuneen kirjautumisen jälkeenLogin Should Fail With Message
-avainsana tarkastaa, että käyttäjä on kirjautumissivulla ja että sivulta löytyy tietty virheviesti. Tarkastuksessa käytetään Page Should Contain-avainsanaa, joka tarkistaa, että sivulta löytyy haluttu tekstiSubmit Credentials
-avainsana painaa "Login"-painiketta käyttämällä Click Button -avainsanaaSet Username
- jaSet Password
-avainsanat syöttävät annetut arvot tiettyihin kenttiin käyttämällä Input Text- ja Input Password-avainsanoja (huomaa, että salasanan kenttä ei ole tavallinen tekstikenttä, vaan salasanakenttä)Create User And Go To Login Page
-avainsana luo sovellukseen käyttäjän ja avaa kirjautumissivun
Testitapauksissa ollaan interaktiossa erilaisten HTML-elementtien, kuten tekstikenttien ja painikkeiden kanssa. Selenium yrittää löytää elementin annettujen argumenttien perusteella käyttäen tiettyä strategiaa. Esimerkiksi Click Button foo
löytää seuraavat button-elementit:
<button id="foo">Click</button>
<button name="foo">Click</button>
<button>foo</button>
Selenium siis etsii button
-elementin, jonka id
-attribuutin arvo, name
-attribuutin arvo, tai sisältö vastaa annettua argumenttia. Kutsu Click Button Login
löytää siis seuraavan src/templates/login.html-tiedostossa määritellyn painikkeen:
<button>Login</button>
Samalla tavoin kutsu Input Text username kalle
löytää id
-attribuutin avulla seuraavan input
-elementin:
<input type="text" name="username" id="username" />
Tee nyt uusi tiedosto home.robot ja lisää sinne seuraavat testitapaukset:
*** Settings ***
Resource resource.robot
Suite Setup Open And Configure Browser
Suite Teardown Close Browser
Test Setup Go To Main Page
*** Test Cases ***
Click Login Link
Click Link Login
Login Page Should Be Open
Click Register Link
Click Link Register new user
Register Page Should Be Open
Testitapausten tulee siis testata, että "Login"- ja "Register new user"-linkkien painaminen avaa oikean sivun. Linkkien klikkaus tapahtuu käyttämällä valmiiksi määriteltyä Click Link -avainsanaa.
Toteuta testin käyttämät avainsanat tiedostoon resource.robot
. Kun suoritat testit, virheilmoitus kertoo mitä avainsanoja on määrittelemättä:
Click Register Link | FAIL |
Setup failed:
No keyword with name 'Go To Main Page' found.
Jatketaan kirjautumiseen liittyvien hyväksymistestien toteuttamista. Katsotaan sitä ennen pikaisesti, miltä AppLibrary-kirjaston toteutus näyttää. Kirjaston määrittelevä luokka AppLibrary
löytyy tiedostosta src/AppLibrary.py, jonka sisältö on seuraava:
import requests
class AppLibrary:
def __init__(self):
self._base_url = "http://localhost:5000"
self.reset_application()
def reset_application(self):
requests.post(f"{self._base_url}/tests/reset")
def create_user(self, username, password):
data = {
"username": username,
"password": password,
"password_confirmation": password
}
requests.post(f"{self._base_url}/register", data=data)
Kirjaston toteutus eroaa jonkin verran edellisestä, komentoriviä hyödyntävän projektin kirjaston toteutuksesta. Erona on, että tässä projektissa testit ja itse sovellus suoritetaan eri prosesseissa, joten testit eivät voi suoraan muuttaa sovelluksen tilaa. Voimme kuitenkin muutta sovelluksen tilaa HTTP-kutsujen avulla jo tutuksi tulleen requests-kirjaston avulla.
Metodi reset_application
lähettää POST-tyyppisen pyynnön sovelluksen polkuun "/tests/reset". Pyynnön käsittelee seuraava funktio:
@app.route("/tests/reset", methods=["POST"])
def reset_tests():
user_repository.delete_all()
return "Reset"
Funktio poistaa kaikki sovelluksen käyttäjät ja näin nollaa sovelluksen tilan.
Metodi create_user
lähettää samankaltaisesti POST-tyyppisen pyynnön sovelluksen polkuun "/register". Pynnön käsittelevä funktio luo uuden käyttäjän, jos se on validi:
@app.route("/register", methods=["POST"])
def handle_register():
username = request.form.get("username")
password = request.form.get("password")
password_confirmation = request.form.get("password_confirmation")
try:
user_service.create_user(username, password, password_confirmation)
return redirect_to_welcome()
except Exception as error:
flash(str(error))
return redirect_to_register()
Lisää User storylle User can log in with valid username/password-combination seuraava testitapaus login.robot-tiedostoon:
Login With Nonexistent Username
# ...
Tehdään seuraavaksi pari muutosta testien suorituksen nopeuttamiseksi. Ensiksi, aseta resource.robot-tiedostossa olevan DELAY
-muuttujan arvoksi 0
. Sen jälkeen, otetaan käyttöön Chrome-selaimen Headless Chrome -variaatio. "Headless"-selainten käyttö on kätevää esimerkiksi automatisoiduissa testeissä, joissa selaimen käyttöliittymä ei ole tarpeellinen. Suorita testit Headless Chromen avulla asettamalla BROWSER
-muuttujan arvoksi headlesschrome
.
HUOM: Headless Chrome vaikeuttaa testien debuggaamista, koska selaimen käyttöliittymä ei ole näkyvissä. Jos testitapauksen suorittaminen epäonnistuu, projektin juurihakemistoon ilmestyy tiedosto selenium-screenshot-*.png, josta on nähtävissä selainikkunan sisältö virhetilanteen hetkellä. Jos tämä tieto ei riitä, voit muuttaa debuggaamista varten DELAY
- ja BROWSER
-muuttujien arvoja.
Tee User storylle A new user account can be created if a proper unused username and a proper password are given seuraavat testitapaukset register.robot-tiedostoon:
Register With Valid Username And Password
# ...
Register With Too Short Username And Valid Password
# ...
Register With Valid Username And Too Short Password
# ...
Register With Nonmatching Password And Password Confirmation
# ...
HUOM tee yksi testitapaus kerrallaan. Testitapausta koodatessa kannattaa suorittaa ainoastaan työn alla olevaa testitapausta täällä olevan ohjeen mukaan, ja kannattanee asettaa headlesschrome
:n sijaan chrome
muuttujan BROWSER
arvoksi jotta näet miten testitapaus etenee.
Käyttäjätunnus ja salasana noudattavat samoja sääntöjä kuin tehtävässä 5, eli:
- Käyttäjätunnuksen on oltava merkeistä a-z koostuva vähintään 3 merkin pituinen merkkijono, joka ei ole vielä käytössä
- Salasanan on oltava pituudeltaan vähintään 8 merkkiä ja se ei saa koostua pelkästään kirjaimista
Laajenna koodiasi siten, että testit menevät läpi. Oikea paikka koodiin tuleville muutoksille on src/services/user_service.py-tiedoston UserService
-luokan metodi validate
.
Muista käynnistää web-palvelin uudestaan, kun teet muutoksia koodiin! Sammuta palvelin näppäilemällä Ctrl+C
terminaali-ikkunaan, jossa web-pavelinta suoritetaan. Käynnistä tämän jälkeen palvelin uudelleen komennolla python3 src/index.py
.
Tee User storylle A new user account can be created if a proper unused username and a proper password are given vielä seuraavat testitapaukset register.robot-tiedostoon:
Login After Successful Registration
# ...
Login After Failed Registration
# ...
Ensimmäisessä testitapauksessa tulee testata, että käyttäjä voi kirjautua sisään onnistuneen rekisteröitymisen jälkeen. Toisessa testitapauksessa taas tulee testata, että käyttäjä ei voi kirjautua sisään epäonnistumiseen rekisteröitymisen jälkeen.
Vinkki: voit halutessasi toteuttaa login_resource.robot-tiedoston, joka määrittelee kirjautumiseen käytettäviä avainsanoja. Voit hyödyntää tämän tiedoston avainsanoja sekä login.robot-, että register.robot-tiedostossa lisäämällä *** Settings ***
-osioon uuden resurssin:
*** Settings ***
Resource resource.robot
Resource login_resource.robot
HUOM: Seuraava osio ei kuulu tehtäviin, eli siinä esitettyjä esimerkkejä ei tarvitse tehdä mihinkään. Ohjeista saattaa kuitenkin olla hyötyä esimerkiksi kurssin miniprojektissa.
Edellisissä tehtävissä luultavasti käynnistit ensin Flask-palvelimen yhdessä terminaali-ikkunassa, jonka jälkeen suoritit testit toisessa terminaali-ikkunassa. Lopuksi, kun testit oli suoritettu, saatoit sammuttaa palvelimen.
Jotta sovelluksen testit pystyisi suorittamaan CI-palvelimella, tulee nämä vaiheet ilmaista komentorivikomennoilla. Tähän tarkoitukseen, voimme käyttää esimerkiksi seuraavaa bash-skriptiä:
#!/bin/bash
# käynnistetään Flask-palvelin taustalle
poetry run python3 src/index.py &
# odetetaan, että palvelin on valmiina ottamaan vastaan pyyntöjä
while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:5000/ping)" != "200" ]];
do sleep 1;
done
# suoritetaan testit
poetry run robot src/e2e
status=$?
# pysäytetään Flask-palvelin portissa 5000
kill $(lsof -t -i:5000)
exit $status
Skriptin voi lisätä esimerkiksi projektin juurihakemiston run_robot_tests.sh-tiedostoon. Tämän jälkeen sen voi suorittaa projektin juurihakemistossa komennolla bash run_robot_tests.sh
. Huomaa, että komento käyttää Unix-komentoja, joten sen suorittaminen ei onnistu esimerkiksi Windows-käyttöjärjestelmän tietokoneella ilman asiaan kuuluvaa komentoriviä. CI-palvelimella tämä ei kuitenkaan koidu ongelmaksi, jos valitsemme virtuaalikoneen käyttöjärjestelmäksi esimerkiksi Ubuntun.
Skriptiä voi hyödyntää CI-palvelimella GitHub Actionsin avulla määrittelemällä sen suorittaminen omana askeleena konfiguraatiossa:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install Poetry
run: pip install poetry
- name: Setup chromedriver
uses: nanasess/setup-chromedriver@master
- run: |
export DISPLAY=:99
chromedriver --url-base=/wd/hub &
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
- name: Install dependencies
run: poetry install
- name: Run robot tests
run: bash run_robot_tests.sh
{% include submission_instructions.md %}