Viikko 3
- tiistai 26.11. 12-14 Ville Nordberg Trail openers ja Kristiina Vainio Houston Inc
- maanantai 2.12. 12-14 Jami Kousa ja Aleksandr Tereshchenko Unity
- maanantai 9.12. 12-14 Kasper Hirvikoski Unity ja Luomus Fossil Database Team
- tiistai 10.12. 12-14 Hannu Kokko Elisa
Allaolevien tehtävien deadline on maanantai 18.11. klo 23:59
Apua tehtävien tekoon kurssin Discord-kanavalla, kampuksella pajassa BK107:
- ma 14-16
- to 13-15
- pe 12-14
Muista myös tämän viikon monivalintatehtävät, joiden deadline on sunnuntai 17.11. klo 23:59:00 .
Tehtävät liittyvät storyjen hyväksymistestauksen automatisointiin tarkoitetun Robot Frameworkin.
Typoja tai epäselvyyksiä tehtävissä?
Tee korjausehdotus editoimalla tätä tiedostoa GitHubissa.
Kurssipalaute
Kurssilla on käytössä normaalin lopussa kerättävän palautteen lisäksi ns. jatkuva palaute: voit antaa milloin vain kurssihenkilökunnalle anonyymiä palautetta osoitteessa https://norppa.helsinki.fi/targets/83083419/feedback
Ongelmia Poetryn kanssa?
Muutamia ohjeita täällä
Tehtävien palauttaminen
Tehtävät palautetaan GitHubiin, sekä merkitsemällä tehdyt tehtävät palautussovellukseen https://study.cs.helsinki.fi/stats/courses/ohtu2024 välilehdelle “my submission”.
Tehtävät 1 ja 5-8 palautetaan jo edellisillä viikoilla käyttämääsi palautusrepositorioon, tehtävän hakemiston viikko3 sisälle. Tehtävät 2-4 palautetaan omaan, uuteen repositorioon.
Katso tarkempi ohje palautusrepositoriota koskien täältä.
1. Tutustuminen Robot Frameworkkiin
Lue täällä oleva Robot Framework -johdanto ja tee siihen liittyvät tehtävät.
2. Web-laskuri
HUOM: jos käytät Dockeria, lue tämä!
Siirrytään seuraavaksi Web-sovellusten maailmaan. Oletuksena on, että hallitset ainakin jossain määrin kurssilta Tietokannat ja Web-ohjelmointi (vanhalta nimeltään Aineopintojen harjoitustyö: Tietokantasovellus) tutun Flask-kirjaston käytön.
Tarkastellaan edellisestä tehtävästä tutun toiminnallisuuden tarjoamaa esimerkkiprojektia, joka löytyy kurssirepositorion alihakemistosta viikko3/webcounter.
Tee tätä ja kahta seuraavaa tehtävää varten kokonaan uusi repositorio, nimeltään esimerkiksi webcounter, ja laita muiden tehtävien palautukseen käyttämäsi palautusrepositorion tiedostoon README.md linkki tätä tehtävää varten tehtyyn repositorioon.
Asenna projektin riippuvuudet komennolla poetry install
ja käynnistä se virtuaaliympäristössä komennolla python3 src/index.py
. Sovelluksen käynnistymisen jälkeen pääset käyttämään sitä avaamalla selaimella osoitteen http://localhost:5001:
Sovellus siis toimii localhostilla eli paikallisella koneellasi portissa 5001.
Saat sammutettua sovelluksen painamalla komentoriviltä ctrl+c
tai ctrl+d
.
Sovelluksen rakenne on pääosin sama mitä kurssin Tietokannat ja Web-ohjelmointi esimerkkisovelluksissa.
Tiedostossa app.py määritellään sivupyyntöjen käsittelijäfunktiot:
from flask import Flask, redirect, render_template
from counter import Counter
app = Flask(__name__)
cnt = Counter()
@app.route("/")
def index():
return render_template("index.html", value=cnt.value)
@app.route("/increment", methods=["POST"])
def increment():
cnt.increase()
return redirect("/")
Sovelluksen HTML-sivupohjat on määritelty hakemistossa templates. Sovelluksen ainoa näkymä näyttää muuttujaan cnt talletetun laskuriolion arvon. Näkymä sisältää myös kaksi nappia, joista “Paina” aiheuttaa POST-pyynnön reitille increment. Reitin käsittelijä kasvattaa laskurin arvoa, ja uudelleenohjaa sovelluksen takaisin juuriosoitteeseen. Nappi “Nollaa” ei tee tällä hetkellä mitään.
Sovellukselle on tehty pari testiä Robot-frameworkilla. Testit suoritetaan normaaliin tapaan komennolla robot src/tests mutta ennen testien suorittamista joudumme tekemään muutaman ekstratempun.
Testeissä on käytössä Selenium WebDriver -kirjasto, jonka avulla on mahdollista simuloida selaimen käyttöä koodista käsin. Seleniumin käyttö Robot Framework -testeissä tapahtuu 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, tai Firefoxia jolloin testit voi suorittaa Geckodriverillä.
Ennen kuin siirryt testien pariin, asenna ChromeDriver tai Geckodriver seuraamalla tätä ohjetta. Fuksiläppärillä ChromeDriver saattaa olla jo asennettuna. Voit tarkistaa tämän komennolla chromedriver --version
Kun ChromeDriver tai GeckoDriver on asennettu onnistuneesti käynnistä web-sovellus edellisen tehtävän tapaan komentoriviltä. Varmista selaimella, että sovellus on päällä.
Avaa uusi terminaali-ikkuna ja suorita projektin testit virtuaaliympäristössä komennolla robot src/tests
jos asensit ChromeDriverin tai robot --variable BROWSER:firefox src/tests
jos asensit Geckodriverin.
Komennon pitäisi suorittaa onnistuneesti kaksi testitapausta, At start the counter is zero
ja When button pressed twice the counter is two
. Testitapausten suoritusta voi seurata aukeavasta selaimen ikkunasta.
Ongelmia?
Tämä ohje saattaa auttaa.
Tutustuminen testeihin
Tiedostossa increment.robot
olevat testit näyttävät seuraavalta:
*** Settings ***
Resource resource.robot
Suite Setup Open And Configure Browser
Suite Teardown Close Browser
*** Test Cases ***
At start the counter is zero
Go To ${HOME_URL}
Title Should Be Laskuri
Page Should Contain nappia painettu 0 kertaa
When button pressed twice the counter is two
Go To ${HOME_URL}
Click Button Paina
Click Button Paina
Page Should Contain nappia painettu 2 kertaa
Jos unohdetaan alun osio Settings, on testien toiminnallisuus aika ilmeinen. Käytössä olevat avainsanat Go To, Click Button ja Page Should Contain ovat SeleniumLibraryn tarjoamia valmiita avainsanoja.
Miten Selenium löytää sivun elementit?
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">Nappi</button>
<button name="foo">Nappi</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, ja kirjoittaa siihen toisena parametrina olevan merkkijonon kalle:
<input type="text" name="username" id="username" />
Tutustuminen testeihin jatkuu
Osassa *** Settings ***
on useita huomionarvoisia seikkoja, rivi
Resource resource.robot
kertoo, että testin tulee ottaa käyttöön tiedostossa resource.robot
tehdyt määritelmät (eli resurssit).
Tiedoston resource.robot
sisältö on seuraava:
*** Settings ***
Library SeleniumLibrary
*** Variables ***
${DELAY} 0.5 seconds
${HOME_URL} http://localhost:5001
${BROWSER} chrome
*** Keywords ***
Open And Configure Browser
Set Selenium Speed ${DELAY}
Open Browser browser=${BROWSER} options=${options}
*** Settings ***
osiossa otetaan käyttöön edellä mainittu SeleniumLibrary-kirjasto, joka siis tuo mukaan lukuisia uusia avainsanoja, joista kaikki on dokumentoitu täällä.
Tiedostossa on myös osio *** Variables ***
missä on mahdollista määritellä muuttujia, jotka ovat kaikkien osion avainsanojen käytössä. Huomaa, että määritellyt muuttujat kirjoitetaan isoilla kirjaimilla, toisin kuin argumentit. Muuttujia kannattaa suosia aina kovakoodattujen arvojen sijaan.
*** Keywords ***
-osiossa on määritellään avainsana Open And Configure Browser
joka alustaa selaimen testejä varten:
- Avainsana käynnistää selaimen käyttämällä SeleniumLibrary-kirjaston Open Browser -avainsanaa antaen
browser
-argumentin arvoksi käytetyn selaimen, joka on oletusarvoisesti chrome. - Tämän lisäksi avainsana asettaa viiveeksi Selenium-komentojen välille
DELAY
-muuttujan arvon käyttämällä Set Selenium Speed -avainsanaa. Pidempi viive helpottaa testien suorituksen seuraamista. - Selaimen ikkunan koon voi asettaa tarvittaessa haluamakseen avainsanalla Set Window Size, nyt käytössä on oletusarvoinen selaimen koko.
Palataan vielä tiedostoon increment.robot
, jonka alun osio *** Settings ***
on seuraava
*** Settings ***
Resource resource.robot
Suite Setup Open And Configure Browser
Suite Teardown Close Browser
*** Test Cases ***
...
Osiossa on käytössä ennestään tuntemattomat Suite Setup
-, Suite Teardown
- -asetukset. Niiden merkitykset ovat seuraavat:
Suite Setup
-asetuksen avulla voimme suorittaa avainsanan ennen tiedoston ensimmäistä testitapausta, eli aluksi siis suoritetaan Open And Configure Browser joka määriteltiin tiedostossaresource.robot
Suite Teardown
-asetuksen avulla voimme suorittaa avainsanan tiedoston viimeisen testitapauksen jälkeen, tapauksessamme suljemme selaimen avainsanalla Close Browser
Huomaa, että toimiakseen testit edellyttävät että sovellus on alussa tilassa missä laskurin arvo on 0. Uudelleenkäynnistä siis sovellus aina ennen testien suorittamista!
Kun olet suorittanut testit onnistuneesti ja tutustunut sovellukseen sekä testeihin on tämä tehtävä tehty.
3. Weblaskurin nollaus
Laajenna sovellusta siten, että nappi “Nollaa” nollaa laskurin arvon.
Tee Robot-testi, joka varmistaa, että nollaaminen toimii. Tee testi tiedostoon reset.robot
, testin näyttää suunilleen seuraavalta
*** Settings ***
Resource resource.robot
Suite Setup Open And Configure Browser
Suite Teardown Close Browser
*** Test Cases ***
When counter has a nonzero value and it is reset the value becomes zero
...
Muista, että toimiakseen valmiina olevat testit edellyttävät että sovellus on alussa tilassa missä laskurin arvo on 0. Uudelleenkäynnistä siis sovellus aina ennen testien suorittamista!
4. Web-sovelluksen testien suorittamien GitHub Actioneissa
Selenium Webdriveria käyttävät Robot-testit on melko helppo suorittaa myös GitHub Actioneissa.
Konfiguraatioihin on tehtävä muutama muutos. Laajennetaan tiedostoa resource.robot
seuraavasti:
*** Settings ***
Library SeleniumLibrary
*** Variables ***
${SERVER} localhost:5001
${DELAY} 0.5 seconds
${HOME_URL} http://${SERVER}
${BROWSER} chrome
${HEADLESS} false
*** Keywords ***
Open And Configure Browser
IF $BROWSER == 'chrome'
${options} Evaluate sys.modules['selenium.webdriver'].ChromeOptions() sys
ELSE IF $BROWSER == 'firefox'
${options} Evaluate sys.modules['selenium.webdriver'].FirefoxOptions() sys
END
IF $HEADLESS == 'true'
Set Selenium Speed 0
Call Method ${options} add_argument --headless
ELSE
Set Selenium Speed ${DELAY}
END
Open Browser browser=${BROWSER} options=${options}
Olemme nyt lisääneet muuttujan HEADLESS jolle arvon true asettamalla voimme suorittaa testit headless-selaimella, eli selaimella missä ei ole käyttöliittymää. Olemme myös määritelleet, että headlessina suoritettaessa Seleniumin viiveeksi asetetaan 0 jotta testit eivät hidastu tarpeettomasti. Headless-suoritus tapahtuu seuraavasti:
robot --variable HEADLESS:true src/tests
GitHub actionien konfiguraatio näyttää seuraavalta:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- 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
Ennen viimeisessä askeleessa tapahtuvaa testien suorittamista suoritetaan valmiiksi määritelty Action setup-chromedriver, joka asentaa chromedriverin GitHub Actionin käyttöön.
Jotta sovelluksen testit pystyisi suorittamaan GitHub Actionissa, tulee nämä askeleet suorittaa komentorivikomennoilla. Tähän tarkoitukseen, voimme käyttää seuraavaa bash-skriptiä run_robot_tests.sh
, joka löytyy tehtäväpohjassa:
#!/bin/bash
echo "Running tests"
# käynnistetään Flask-palvelin taustalle
poetry run python3 src/index.py &
echo "started Flask server"
# odetetaan, että palvelin on valmiina ottamaan vastaan pyyntöjä
while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:5001)" != "200" ]];
do sleep 1;
done
echo "Flask server is ready"
# suoritetaan testit
poetry run robot --variable HEADLESS:true src/tests
status=$?
# pysäytetään Flask-palvelin portissa 5001
kill $(lsof -t -i:5001)
exit $status
Pushaa tehtävän repositorio GitHubiin ja varmista, että GitHub Actions suorittaa testit onnistuneesti.
Laajenna vielä sovellusta siten, että siihen tulee mahdollisuus asettaa laskuri haluttuun arvoon. Sovellus voi näyttää laajennuksen jälkeen seuraavalta:
Tee ominaisuudelle Robot-testit.
Kertaa tarvittaessa täältä se miten Selenium löytää sivun elementit.
Ohjeita lomakkeen käsittelyyn kurssin Tietokannat ja Webohjelmointi materiaalissa. HUOM lomakkeen datan vastaanottamisen jälkeen tulee tehdä redirect
samoin kuin nappien painallusten käsittelyssä, ks. Post/Redirect/Get.
Tee ominaisuudelle Robot-testit.
Jos lomakkeessa on käytössä syötekenttä, jonka attribuutti name on arvoltaan value:
<input type="text" name="value" />
Robot-testi voi kirjoittaa kenttään arvon 10 avainsanan Input Text avulla seuraavasti:
Input Text value 10
Robot Framework -testien debuggaaminen
Ennen kuin edetään seuraavaan tehtäväsarjaan, nostetaan esiin tärkeä teema.
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
Jos testit eivät mene läpi, generoi Selenium hakemistoon kuvakaappauksen tilanteesta, siitä tilanteesta missä testi havaitsee ongelman. Tämän viikon tehtävää 7 tehdessäni törmäsin seuraavaan:
Tässä tapauksessa ongelma oli erittäin helppo korjata.
Tutustutaan seuraavaksi muihin tekniikoihin, jotka helpottavat ja nopeuttavat virheiden metsästystä.
Suoritettavien testien lukumäärän rajoittaminen
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.
Ohjelman suorituksen seuraaminen
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.
5. WebLogin, osa 1
Tarkastellaan nyt rakenteeltaan hieman monimutkaisempaa Web-sovellusta, joka löytyy kurssirepositorion hakemistossa viikko3/web-login.
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älkeen pääset käyttämään sitä avaamalla selaimella osoitteen http://localhost:5001. Sovellus siis toimii localhostilla eli paikallisella koneellasi portissa 5001.
Sovellus on hyvin yksinkertainen, se tarjoaa vain kaksi toimintoa:
- käyttäjä voi rekisteröityä, eli luoda järjestelmään käyttäjätunnuksen
- rekisteröitynyt käyttäjä voi kirjautua järjestelmään
Tutustutaan seuraavaksi sovelluksen rakenteeseen. Sovellus noudattaa ns. kerrosarkkitehtuuria eli se on rakenteeltaan samanlainen kuin kurssin Ohjelmistotekniikka referenssisovellus.
Sovelluksen käyttöliittymä on toteutettu edellisten tehtävien sovelluksen tapaan tiedostoon app.py
sekä hakemistoon templates
. Ohjelman sovelluslogiikka on sijoitettu omaan luokkaansa 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, kuten find_all
, create
ja delete
kautta. 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 PostgreSQL-tietokantaan, ei tämä vaatisi muutoksia luokan ulkopuolelle.
Seuraavassa vielä lyhyt katsaus sovelluksen käyttöliittymän, eli reitin käsittelijöiden sekä näkymien generoinnin toiminnasta.
Polulle “/” eli sovelluksen juureen, osoitteeseen http://localhost:5001 tulevat pyynnöt käsittelee 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:
{% 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 %}
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
-olion metodia check_credentials
. Jos kirjautuminen onnistuu, ohjataan käyttäjä “/ohtu”-polun sivulle. Jos se epäonnistuu, check_credentials
-metodi aiheuttaa poikkeuksen, 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.
Tutustuminen testeihin
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:5001
${DELAY} 0.5 seconds
${HOME_URL} http://${SERVER}
${LOGIN_URL} http://${SERVER}/login
${REGISTER_URL} http://${SERVER}/register
${BROWSER} chrome
${HEADLESS} false
*** Keywords ***
Open And Configure Browser
IF $BROWSER == 'chrome'
${options} Evaluate sys.modules['selenium.webdriver'].ChromeOptions() sys
ELSE IF $BROWSER == 'firefox'
${options} Evaluate sys.modules['selenium.webdriver'].FirefoxOptions() sys
END
IF $HEADLESS == 'true'
Set Selenium Speed 0
Call Method ${options} add_argument --headless
ELSE
Set Selenium Speed ${DELAY}
END
Open Browser browser=${BROWSER} options=${options}
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}
Tiedoston sisältö on samankaltainen kuin edellisissä tehtävissä. Tällä kertaaa
*** Settings ***
osiossa on otettu SeleniumLibrary-kirjaston lisäksi käyttöön myös projektin oma AppLibrary.py
-kirjasto, joka määrittelee kaksi projektissa tarvittavaa avainsanaa, Reset Application
ja Create User
.
*** Keywords ***
-osiossa on määritelty myös muutama yleiskäyttöinen avainsana:
Login Page Should Be Open
jaMain Page Should Be Open
, joiden 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 Reset Application Create User And Go To Login Page
Edellisten tehtävien testien lisäksi nyt on käytössä myös Test Setup
, joka suorittaa avainsanan ` Reset Application Create User And Go To Login Page` ennen jokaista testiä.
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ä)- ` Reset Application Create User And Go To Login Page` -avainsana tyhjentää sovelluksen “tietokannan” eli sinne luodut käyttäjät, luo sovellukseen uuden käyttäjän ja avaa kirjautumissivun
Kertaa tarvittaessa täältä se miten Selenium löytää sivun elementit.
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 Reset Application And Go To Starting 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
*** Keywords ***
Reset Application And Go To Starting Page
Reset Application
Go To Starting Page
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.
Kun suoritat testit, virheilmoitus kertoo mitä avainsanoja on määrittelemättä:
Click Register Link | FAIL |
Setup failed:
No keyword with name 'Go To Starting Page' found.
Toteuta testin käyttämät määrittelemättömät avainsanat.
HUOM ideana on, että avainsana Go To Starting Page
vie sovelluksen polkuun / eli aloitussivulle.
6. WebLogin, osa 2
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:5001"
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)
On oleellista, että testit alkavat aina samasta tilasta, erityisesti, että sovelluksen tietokannan tila on testien alussa hyvin tunnettu.
Metodin reset_application
määrittelemä avainsana 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. Kyseessä on siis ainoastaan testien käyttöön toteutettu tapa nollata tietokanta.
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
# ...
7. WebLogin, osa 3
Tee User storylle A new user account can be created if a proper unused username and a proper password are given seuraavat testitapaukset uuteen tiedostoon register.robot
:
*** Settings ***
Resource resource.robot
Suite Setup Open And Configure Browser
Suite Teardown Close Browser
Test Setup Reset Application Create User And Go To Register Page
*** Test Cases ***
Register With Valid Username And Password
# ...
Register With Too Short Username And Valid Password
# ...
Register With Valid Username And Too Short Password
# ...
Register With Valid Username And Invalid Password
# salasana ei sisällä halutunlaisia merkkejä
# ...
Register With Nonmatching Password And Password Confirmation
# ...
Register With Username That Is Already In Use
#
*** Keywords ***
#...
Käyttäjätunnus ja salasana noudattavat seuraavia sääntöjä:
- 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
.
Pro tips:
- Etene yksi testitapaus ja sen toteuttama koodi kerrallaan
- Kertaa tarvittaessa täältä se miten Selenium löytää sivun elementit
- Ota mallia kirjautumisen testeistä!
- Muista tämä, ja sieltä erityisesti tämä
8. WebLogin, osa 4
Tee User storylle A new user account can be created if a proper unused username and a proper password are given vielä seuraava testitapaus tiedostoon register.robot
:
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. Koska käyttäjä kirjautuu automaattisesti kun se luodaan, testin tulee kirjautua ulos ja varmistaa sitten että uusi kirjautuminen onnistuu.
Tehtävien palautus
Laita palautusrepositoriosi tiedostoon README.md linkki tehtäviä 2-4 varten tehtyyn webcounter-repositoroosi.
Tehtävien palautus
Pushaa kaikki tekemäsi tehtävät (paitsi ne, joissa mainitaan, että tehtävää ei palauteta mihinkään) GitHubiin palautusrepositorioosi ja merkkaa tekemäsi tehtävät palautussovellukseen https://study.cs.helsinki.fi/stats/courses/ohtu2024