Viikko 3
⚠️ Viikon tehtävien palautuksen deadline on tiistai 19.11. klo 23:59. Tehtävät on tarkoitus tehdä joko pajassa tai omatoimisesti.
Tehtävät palautetaan GitHubin avulla Labtooliin rekisteröimääsi repositorioon. Muista pushata tehtävät GitHubiin ennen palautuksen deadlinea! Klo 00 jälkeen tulevia repositorion päivityksiä ei huomioida pisteytyksessä, eli ne tuovat 0 pistettä.
Palautuksesta saadut pisteet ja palaute löytyy Labtoolista viimeistään deadlinea vastaavan viikon loppuun mennessä. Muista tarkistaa saamasi pisteet ja palaute. Jos pisteytyksestä herää kysymyksiä, lähetä viesti Labtoolin kautta.
Tämän viikon tehtävien palautuksesta on tarjolla 1 piste ja harjoitustyön palautuksesta 2 pistettä.
Tee palautettavia tehtäviä varten repositorion sisällä olevaan hakemistoon laskarit uusi alihakemisto viikko3.
UML
Ohjelmistojen dokumentoinnissa ja sovelluksen suunnittelun yhteydessä on usein tapana visualisoida ohjelman rakennetta ja toimintaa UML-kaavioilla.
UML tarjoaa lukuisia erilaisia kaaviotyyppejä, hyödynnämme kurssilla kuitenkin näistä ainoastaan kolmea.
Luokkakaaviot
Kurssilla Tietokantojen perusteet olet saattanut jo tutustua luokkakaavioiden käyttöön. Luokkakaavioiden käyttötarkoitus on ohjelman luokkien ja niiden välisten suhteiden kuvailu. Todo-sovelluksen oleellista tietosisältöä edustavat käyttäjää vastaava luokka User
:
class User:
def __init__(self, username, password):
self.username = username
self.password = password
ja tehtävää vastaava luokka Todo
:
import uuid
class Todo:
def __init__(self, content, done=False, user=None, todo_id=None):
self.content = content
self.done = done
self.user = user
self.id = todo_id or str(uuid.uuid4())
def set_done(self):
self.done = True
Jokaiseen todoon liittyy yksi käyttäjä, ja yksittäiseen käyttäjään liittyviä todoja voi olla useita. Tilannetta kuvaa seuraava luokkakaavio
Luokkakaavioon on nyt merkitty molempien luokkien oliomuuttujat sekä metodit. Yleensä ei ole mielekästä kuvata luokkia tällä tarkkuudella, eli luokkakaavioihin riittää merkitä luokan nimi:
Luokkien tarkemmat yksityiskohdat selviävät koodia katsomalla tai docstring-dokumentoinnista, johon tutustutaan viikolla 6.
Riippuvuus
UML-kaavioissa olevat “viivat” kuvaavat luokkien olioiden välistä pysyvää yhteyttä. Joissain tilanteissa on mielekästä merkata kaavioihin myös ei-pysyvää suhdetta kuvaava katkoviiva, eli riippuvuus.
Eräs tällainen tilanne voisi olla Unicafe-ruokalan kassapäätteen toiminnallisuudesta vastaava koodi. Koodissa on kaksi luokkaa Maksukortti
ja Kassapaate
, joiden välillä ei ole pysyvää yhteyttä.
Maksukortin koodi on seuraava:
class Maksukortti:
def __init__(self, saldo):
self.saldo = saldo
def lataa_rahaa(self, lisays):
self.saldo += lisays
def ota_rahaa(self, maara):
if self.saldo < maara:
return False
self.saldo -= maara
return True
Kuten huomataan, koodissa ei mainita kassapäätettä millään tavalla.
Kassapäätteen hieman lyhennetty koodi on seuraava:
EDULLISEN_HINTA = 2.5
MAUKKAAN_HINTA = 4.3
class Kassapaate:
def __init__():
self.edulliset = 0
self.maukkaat = 0
def syo_edullisesti(self, kortti):
if kortti.saldo() < EDULLISEN_HINTA:
return False
kortti.ota_rahaa(EDULLISEN_HINTA);
self.edulliset += 1
return True
def syo_maukkaasti(self, kortti):
# ...
def lataa_rahaa_korttille(self, kortti, summa):
if summa < 0:
return
kortti.lataa_rahaa(summa)
self.rahaa += summa
Kassapääte käyttää maksukortteja hetkellisesti lounaiden maksamisen ja rahan lataamisen yhteydessä. Kassapääte ei kuitenkaan muista pysyvästi yksittäisiä maksukortteja. Tämän takia kassapäätteellä on riippuvuus maksukortteihin, mutta ei kuitenkaan normaalia yhteyttä, sillä UML-kaavioon merkattu yhteys viittaa pysyvään, ajallisesti pidempikestoiseen suhteeseen.
Tilannetta kuvaava luokkakaavio on seuraava:
Riippuvuus siis kuvataan katkoviivallisena nuolena, joka kohdistuu siihen luokkaan mistä ollaan riippuvaisia. Riippuvuuteen ei merkitä numeroa toisin kuin yhteyteen.
Tarkastellaan toisena esimerkkinä riippuvuudesta todo-sovelluksen sovelluslogiikasta vastaavaa luokkaa TodoService
, jonka koodi hieman lyhennettynä näyttää seuraavalta:
class TodoService:
def __init__(self, todo_repository, user_repository):
self._user = None
self._todo_repository = todo_repository
self._user_repository = user_repository
def create_todo(self, content):
todo = Todo(content=content, user=self._user)
return self._todo_repository.create(todo)
def get_undone_todos(self):
if not self._user:
return []
todos = self._todo_repository.find_by_username(self._user.username)
undone_todos = filter(lambda todo: not todo.done, todos)
return list(undone_todos)
# ...
Sovelluslogiikkaa hoitava olio tuntee kirjautuneen käyttäjän, mutta pääsee käsiksi kirjautuneen käyttäjän todoihin ainoastaan todo_repository
-olion välityksellä. Tämän takia luokalla ei ole yhteyttä luokkaan Todo
, luokkien välillä on kuitenkin riippuvuus, sillä sovelluslogiikka käsittelee metodeissaan todo-olioita.
Merkitään luokkakaavioon seuraavasti:
Riippuvuuksien merkitseminen luokkakaavioihin ei ole välttämättä kovin oleellinen asia, niitä kannattaa merkitä jos ne tuovat esille tilanteen kannalta jotain oleellista.
Perintä
Luokkien perintähierarkian ilmaisemisessa käytetään nuolia, joissa on valkoiset päät. Esim. jos Todo-sovelluksessa olisi normaalin käyttäjän eli luokan User
perivää ylläpitäjää kuvaava luokka SuperUser
, merkattaisiin se luokkakaavioon seuraavasti:
Työkaluja luokkakaavioiden piirtämiseen
GitHubin markdown-tiedostoissa erilaisia kaavioita voi toteuttaa kätevästi Mermaid-syntaksin avulla. Kaavioista ei tarvitse erillisiä kuvatiedostoja, vaan ne voi määritellä suoraan markdown-tiedostoon:
## Sovelluslogiikka
Sovelluksen loogisen tietomallin muodostavat luokat User ja Todo, jotka kuvaavat käyttäjiä ja käyttäjien tehtäviä:
```mermaid
classDiagram
Todo "*" --> "1" User
class User{
username
password
}
class Todo{
id
content
done
}
```
Dokumentaation Luokkakaavio-osiosta löytyy tarkemmat ohjeet luokkakaavioiden toteuttamiseen. Mallia kaavioiden käyttämisessä markdown-tiedostossa voi ottaa referenssisovelluksen arkkitehtuuri-dokumentaatiosta. Vaihtoehtoisesti, luokkakaavioiden toteuttamiseen sopii myös esimerkiksi https://app.diagrams.net/.
Tehtävä 1: Monopoli
Monopoli on varmasti kaikkien tuntema lautapeli.
Tehdään luokkakaavio, joka kuvaa pelissä olevia asioita ja niiden suhteita. Aloitetaan sellaisella, joka ei kuvaa peliä vielä kokonaisuudessaan vaan sisältää vasta seuraavat elementit:
Monopolia pelataan käyttäen kahta noppaa. Pelaajia on vähintään 2 ja enintään 8. Peliä pelataan pelilaudalla joita on yksi. Pelilauta sisältää 40 ruutua. Kukin ruutu tietää, mikä on sitä seuraava ruutu pelilaudalla. Kullakin pelaajalla on yksi pelinappula. Pelinappula sijaitsee aina yhdessä ruudussa.
Tämä alustava kaavio näyttää seuraavalta:
(Luokkien keskinäisiä suhteita voisi kuvata hieman tarkemminkin, mutta tämä taso riittää meille.) Kaavio on toteutettu markdown-tiedostoon Mermaid-syntaksin mukaisesti seuraavaan tapaan:
## Monopoli, alustava luokkakaavio
```mermaid
classDiagram
Monopolipeli "1" -- "2" Noppa
Monopolipeli "1" -- "1" Pelilauta
Pelilauta "1" -- "40" Ruutu
Ruutu "1" -- "1" Ruutu : seuraava
Ruutu "1" -- "0..8" Pelinappula
Pelinappula "1" -- "1" Pelaaja
Pelaaja "2..8" -- "1" Monopolipeli
```
Laajennetaan nyt luokkakaaviota tuomalla esiin seuraavat asiat:
Ruutuja on useampaa eri tyyppiä:
- Aloitusruutu
- Vankila
- Sattuma ja yhteismaa
- Asemat ja laitokset
- Normaalit kadut (joihin liittyy nimi)
Monopolipelin täytyy tuntea sekä aloitusruudun että vankilan sijainti.
Jokaiseen ruutuun liittyy jokin toiminto.
Sattuma- ja yhteismaaruutuihin liittyy kortteja, joihin kuhunkin liittyy joku toiminto.
Toimintoja on useanlaisia. Ei ole vielä tarvetta tarkentaa toiminnon laatua.
Normaaleille kaduille voi rakentaa korkeintaan 4 taloa tai yhden hotellin. Kadun voi omistaa joku pelaajista. Pelaajilla on rahaa.
Lisää tämän viikon tehtäviä varten repositoriosi laskarit hakemistoon hakemisto viikko3 ja lisää toteuttamasi kaavio sinne. Jos hyödynnät Mermaid-syntaksia kaavion toteuttamisessa, voit sijoittaa kaavion markdown-tiedostoon.
Pakkauskaavio
Todo-sovelluksen koodi on sijoitettu hakemistoihin seuraavasti:
Hakemistorakennetta voidaan kuvata UML:ssä pakkauskaaviolla:
Pakkausten välille on merkitty riippuvuudet katkoviivalla. Pakkaus ui riippuu pakkauksesta services sillä ui-pakkauksen luokat käyttävät services-pakkauksen luokkaa TodoService
, joka vastaa sovelluksen sovelluslogiikasta.
Vastaavasti pakkaus services riippuu pakkauksesta repositories sillä sen luokka TodoService
käyttää repositories-pakkauksen luokkia TodoRepository
ja UserRepository
.
Pakkauskaavioihin on myös mahdollista merkitä pakkausten sisältönä olevia luokkia normaalin luokkakaaviosyntaksin mukaan:
Sovelluksen koodi on organisoitu kerrosarkkitehtuurin periaatteiden mukaan. Asiasta lisää hieman täällä. Myös Ohjelmoinnin jatkokurssin osa 10 luku Laajemman sovelluksen kehittäminen voi olla hyödyllinen.
Sekvenssikaaviot
Luokka- ja pakkauskaaviot kuvaavat ohjelman rakennetta. Ohjelman toiminta ei kuitenkaan tule niistä ilmi millään tavalla.
Esimerkiksi Unicafe-ruokalan maksukortin ja kassapäätteen välistä suhdetta kuvaava luokkakaavio voisi näyttää seuraavalta:
Vaikka kaavioon on merkitty metodien nimet, ei ohjelman toimintalogiikka, esimerkiksi mitä tapahtuu kun kortilla ostetaan edullinen lounas, selviä kaaviosta millään tavalla.
Sekvenssikaaviot on alunperin kehitetty kuvaamaan verkossa olevien ohjelmien keskinäisen kommunikoinnin etenemistä. Sekvenssikaaviot sopivat kohtuullisen hyvin kuvaamaan myös sitä, miten ohjelman oliot kutsuvat toistensa metodeja suorituksen aikana.
Koodia katsomalla näemme, että lounaan maksaminen tapahtuu siten, että ensin kassapääte kysyy kortin saldoa ja jos se on riittävä, vähentää kassapääte lounaan hinnan kortilta ja palauttaa True:
EDULLISEN_HINTA = 2.5
class Kassapaate:
# ...
def syo_edullisesti(self, kortti):
if kortti.saldo < EDULLISEN_HINTA:
return False
kortti.ota_rahaa(EDULLISEN_HINTA)
self.edulliset += 1
return True
# ...
Sekvenssikaaviona kuvattuna tilanne näyttää seuraavalta:
Sekvenssikaaviossa oliot kuvataan laatikoina, joista lähtee alaspäin olion “elämänlanka”. Kaaviossa aika etenee ylhäältä alas. Metodikutsut kuvataan nuolena, joka yhdistää kutsujan ja kutsutun olion elämänlangat. Paluuarvo merkitään katkoviivalla. Attribuutin arvon lukeminen tai asettaminen voidaan kuvata kaaviossa metodikutsun tavoin. Tästä esimerkkinä kaavion saldo
-attribuutin lukeminen.
Jos saldo ei riitä, etenee suoritus seuraavan sekvenssikaavion tapaan:
Tarkastellaan hieman monimutkaisempaa tapausta, yrityksen palkanhallinnasta vastaavaa ohjelmaa:
class Henkilo:
def __init__(self, nimi, palkka, tilinumero):
self.nimi = nimi
self.palkka = palkka
self.tilinumero = tilinumero
class Henkilostorekisteri:
def __init__(self):
self._henkilot = {}
self._pankki = PankkiRajapinta()
def lisaa(self, henkilo):
self._henkilot[henkilo.nimi] = henkilo
def suorita_palkanmaksu(self):
for nimi in self._henkilot:
henkilo = self._henkilot[nimi]
self._pankki.maksa_palkka(henkilo.tilinumero, henkilo.palkka)
def aseta_palkka(self, nimi, uusi_palkka):
henkilo = self._henkilot[nimi]
henkilo.palkka = uusi_palkka
class PankkiRajapinta:
# ...
def maksa_palkka(tilinumero, summa):
# suorittaa maksun verkkopankin internet-rajapinnan avulla
# yksityiskohdat piilotettu
Sekvenssikaaviot siis kuvaavat yksittäisten suoritusskenaarioiden aikana tapahtuvia asioita. Kuvataan nyt seuraavan pääohjelman aikaansaamat tapahtumat:
def main():
rekisteri = Henkilostorekisteri()
arto = Henkilo("Hellas", 1200, "1234-12345")
rekisteri.lisaa(arto)
sasu = Henkilo("Tarkoma", 6500, "4455-123123")
rekisteri.lisaa(sasu)
rekisteri.aseta_palkka("Hellas", 3500)
rekisteri.suorita_palkanmaksu()
Sekvenssikaavio on seuraavassa:
Kaavio alkaa tilanteesta, jossa Henkilostorekisteri
-luokan olio on jo luotu, mutta henkilöolioita ei vielä ole olemassa.
Toiminta alkaa siitä, kun pääohjelma eli main luo henkilön nimeltä arto
. Seuraavaksi main
-funktiosta kutsutaan rekisterin metodia lisaa
, jolle annetaan argumentiksi luotu henkilöolio. Vastaava toistuu kun main
luo uuden henkilön ja lisää sen rekisteriin.
Seuraavana toimenpiteenä main kasvattaa arton palkkaa kutsumalla rekisterin metodia aseta_palkka
. Tämä saa aikaan sen, että rekisteri
asettaa arto
-olion palkka
-attribuutille uuden arvon. Rekisterin viivaan on merkitty paksunnus, joka korostaa, että attribuutille on asetettu arvo. Huomaa, että olion attribuutin asettamista voidaan kuvata metodikutsun tavoin.
Viimeinen ja monimutkaisin toiminnoista käynnistyy, kun main
kutsuu rekisterin metodia suorita_palkanmaksu
. Rekisteri kysyy ensin arton tilinumeroa ja palkkaa ja kutsuu paluuarvoina olevilla tiedoilla pankin metodia maksa_palkka
ja sama toistuu sasu
-olion kohdalla.
Sekvenssikaaviot eivät ole optimaalinen tapa ohjelman suorituslogiikan kuvaamiseen. Ne sopivat jossain määrin olio-ohjelmien toiminnan kuvaamiseen, mutta esim. funktionaalisella tyylillä tehtyjen ohjelmien kuvaamisessa ne ovat varsin heikkoja.
Tietynlaisten tilanteiden kuvaamiseen ohjelmoinnin perusteissakin käsitellyt vuokaaviot voivat sopia paremmin.
Voit halutessasi lukea lisää sekvenssikaavioista kurssin vanhan version materiaalista.
Työkaluja sekvenssikaavioiden piirtämiseen
Myös sekvenssikaavioiden toteuttaminen onnistuu kätevästi Mermaid-syntaksin avulla. Dokumentaation Sekvenssikaavio-osiosta löytyy tarkemmat ohjeet sekvenssikaavioiden toteuttamiseen. Mallia kaavioiden käyttämisessä markdown-tiedostossa voi ottaa referenssisovelluksen arkkitehtuuri-dokumentaatiosta. Vaihtoehtoisesti, sekvenssikaavioiden toteuttamiseen sopii myös esimerkiksi https://www.websequencediagrams.com/.
Tehtävä 2: sekvenssikaavio
Tarkastellaan HSL-matkakorttien hallintaan käytettävää koodia.
Kuvaa sekvenssikaaviona koodin main
-funktion suorituksen aikaansaama toiminnallisuus.
Muista, että sekvenssikaaviossa tulee tulla ilmi kaikki mainin suorituksen aikaansaamat olioiden luomiset ja metodien kutsut!
class Kioski:
def osta_matkakortti(self, nimi, arvo = None):
uusi_kortti = Matkakortti(nimi)
if arvo:
uusi_kortti.kasvata_arvoa(arvo)
return uusi_kortti
class Matkakortti:
def __init__(self, omistaja):
self.omistaja = omistaja
self.pvm = 0
self.kk = 0
self.arvo = 0
def kasvata_arvoa(self, maara):
self.arvo += maara
def vahenna_arvoa(self, maara):
self.arvo -= maara
def uusi_aika(self, pvm, kk):
self.pvm = pvm
self.kk = kk
class Lataajalaite:
def lataa_arvoa(self, kortti, maara):
kortti.kasvata_arvoa(maara)
def lataa_aikaa(self, kortti, pvm, kk):
kortti.uusi_aika(pvm, kk)
RATIKKA = 1.5
HKL = 2.1
SEUTU = 3.5
class Lukijalaite:
def osta_lippu(self, kortti, tyyppi):
hinta = 0
if tyyppi == 0:
hinta = RATIKKA
elif tyyppi == 1:
hinta = HKL
else:
hinta = SEUTU
if kortti.arvo < hinta:
return False
kortti.vahenna_arvoa(hinta)
return True
class HKLLaitehallinto:
def __init__(self):
self._lataajat = []
self._lukijat = []
def lisaa_lataaja(self, lataaja):
self._lataajat.append(lataaja)
def lisaa_lukija(self, lukija):
self._lukijat.append(lukija)
def main():
laitehallinto = HKLLaitehallinto()
rautatietori = Lataajalaite()
ratikka6 = Lukijalaite()
bussi244 = Lukijalaite()
laitehallinto.lisaa_lataaja(rautatietori)
laitehallinto.lisaa_lukija(ratikka6)
laitehallinto.lisaa_lukija(bussi244)
lippu_luukku = Kioski()
kallen_kortti = lippu_luukku.osta_matkakortti("Kalle")
rautatietori.lataa_arvoa(kallen_kortti, 3)
ratikka6.osta_lippu(kallen_kortti, 0)
bussi244.osta_lippu(kallen_kortti, 2)
if __name__ == "__main__":
main()
Lisää toteuttamasi kaavio repositoriosi laskarit/viikko3-hakemistoon. Jos hyödynnät Mermaid-syntaksia kaavion toteuttamisessa, voit sijoittaa kaavion markdown-tiedostoon.
Tehtävien suorittaminen ja Invoke
Projekteissa on useimmiten monia toistuvasti suoritettavia tehtäviä, joita suoritetaan terminaalissa annettavien komentojen muodossa. Luultavasti tärkein näistä tehtävistä on sovelluksen käynnistäminen, joka saattaa tapahtua esimerkiksi komennolla python3 src/index.py
.
Tehtäviin liittyvien komentojen kirjoittaminen käsin käy helposti työlääksi. Tämä tulee ilmi etenkin tilanteissa, joissa komennot ovat monimutkaisia, tai vaativat useampien komentojen suorittamista. Ongelman ratkaisemiseksi on kehitetty työkaluja, joiden avulla tehtäviä voi määritellä ja suorittaa terminaalissa helposti. Tutustutaan seuraavaksi erääseen tähän käyttötarkoitukseen soveltuvaan työkaluun nimeltä Invoke.
Asennus
Invoken asennus projektiin onnistuu komennolla:
poetry add invoke
Tehtävien määritteleminen
Invoken avulla määritellyt tehtävät toteutetaan projektin juurihakemiston tasks.py-tiedostoon. Tehtävät ovat funktioita, joissa käytetään @task
-dekoraattoria. Toteutetaan esimerkkinä tasks.py-tiedostoon tehtävä nimeltä foo, joka tulostaa tekstin “bar”:
from invoke import task
@task
def foo(ctx):
print("bar")
Tämän erittäin hyödyllisen tehtävän voi suorittaa terminaalissa komennolla:
poetry run invoke foo
Komennon suorittamisen pitäisi tulostaa komentoriville teksti “bar”. Tehtävät voi siis suorittaa terminaalissa komennolla, joka on muotoa poetry run invoke <tehtävä>
. Huomaa, että poetry run
-komennon ansiosta tehtävät suoritetaan virtuaaliympäristössä.
Toteutetaan seuraavaksi foo-tehtävän lisäksi tehtävä, josta on oikeasti hyötyä. Tarvitsemme tehtävän, joka suorittaa sovelluksemme komennolla python3 src/index.py
. Annetaan tälle tehtävälle nimeksi start:
from invoke import task
@task
def foo(ctx):
print("bar")
@task
def start(ctx):
ctx.run("python3 src/index.py", pty=True)
Voimme suorittaa tehtävässä komentorivikomennon käyttämällä parametrina saadun Context-olion metodia run. Tehtävän suorittaminen onnistuu komennolla poetry run invoke start
. Huomaa, että pty=True
-argumentti on erityisen tärkeä komentorivikäyttöliittymässä, jotta sovelluksen syötteet ja tulosteet toimivat odotetulla tavalla.
Voimme listata kaikki projektissa käytössä olevat tehtävät komennolla:
poetry run invoke --list
Huomioita tehtävien nimeämisestä
Jos tehtävän määrittelevän funktion nimi on snake_case-formaatissa, on komentoriviltä suoritettavan tehtävän nimi kebab-case-formaatissa. Esimerkiksi seuraavasti nimetty tehtävä:
from invoke import task
@task
def lorem_ipsum(ctx):
print("Lorem ipsum")
Suoritettaisiin komennolla poetry run invoke lorem-ipsum
. Jos olet epävarma käytössä olevien tehtävien nimistä, voit aina listata ne komennolla poetry run invoke --list
.
Toisistaan riippuvaiset tehtävät
Coverage-ohjeissa tutustuimme testikattavuuden keräämiseen ja raportin muodostamiseen sen perusteella. Jos haluamme muodostaa testikattavuusraportin, tulee testikattavuus olla ensin kerätty. Käyttötarkoitukseen soveltuvilla tehtävillä voisi olla määrittelyt seuraavasti:
from invoke import task
@task
def coverage(ctx):
ctx.run("coverage run --branch -m pytest", pty=True)
@task()
def coverage_report(ctx):
ctx.run("coverage html", pty=True)
Jos suoritamme tehtävän coverage-report ennen coverage-tehtävän suorittamista, raportti sisältää joko vanhat testikattavuustiedot, tai kohtaamme virheen, joka valittaa testikattavuustietojen puutosta. Voisimme suorittaa komennot peräkkäin komennolla:
poetry run invoke coverage coverage-report
Helpompaa on kuitenkin määritellä coverage-report-tehtävä riippuvaiseksi coverage-tehtävästä. Tämä onnistuu antamalla @task
-dekoraattorille argumentiksi coverage-tehtävän funktio:
from invoke import task
@task
def coverage(ctx):
ctx.run("coverage run --branch -m pytest", pty=True)
@task(coverage)
def coverage_report(ctx):
ctx.run("coverage html", pty=True)
Nyt komento poetry run invoke coverage-report
suorittaa ensin tehtävän coverage, jonka jälkeen suoritetaan itse tehtävä coverage-report.
Jos haluat, että oletus työpöytäsovelluksesi avaisisi joka kertaa uudelleen tuodun raportin, voit laajentaa coverage-report task:in näin:
from subprocess import call
from sys import platform
@task(coverage)
def coverage_report(ctx):
ctx.run("coverage html", pty=True)
if platform != "win32":
call(("xdg-open", "htmlcov/index.html"))
Harjoitustyö
Tämän viikon aikana aloitetaan harjoitustyön toteutus ja testaaminen. Ohjelman tulee edistyä jokaisella viikolla tasaisesti. Jos ohjelma tulee valmiiksi jo ennen loppupalautusta valmistaudu laajentamaan sitä saadaksesi ohjelman edistymisestä pisteet. Tarkoitus on edistää projektia tasaisesti kurssiviikkojen aikana.
Tämän viikon harjoitustyön palautuksesta on tarjolla 2 pistettä. Viikkopisteiden lisäksi kannattaa pitää mielessä harjoitustyön lopullisen palautuksen arvosteluperusteet.
Varoitus: pip
Olet kenties saattanut aiemmin asentaa Pythonin tarvitsemia riippuvuuksia pip-komennolla. Älä kuitenkaan käytä pipiä tällä kurssilla sillä jos teet niin, teet 99.9% todennäköisyydellä jotain väärin. Asenna riippuvuudet tällä kurssilla Poetryn avulla.
Harjoitustyö 1: Poetry projektin alustaminen
Alusta repositoriosi juureen Poetry-projekti edellisen viikon Poetry-ohjeiden mukaisesti. Repositorion rakenne tulee olla seuraava:
laskarit/
...
dokumentaatio/
...
src/
...
pyproject.toml
poetry.lock
README.md
...
Projektin koodi tulee sijoittaa repositorion src-hakemistoon. Koodia kannattaa tarpeen mukaan jakaa hakemiston sisällä alihakemistoihin. Mallia voi ottaa referenssisovelluksesta.
Voit myös halutessasi alustaa projektin haluamaasi alihakemistoon, esimerkiksi seuraavasti:
laskarit/
...
todo-app/
dokumentaatio/
...
src/
...
pyproject.toml
poetry.lock
...
README.md
...
HUOM: src-hakemiston alahakemistoissa (ei siis itse src-hakemistossa) tulee olla tyhjät __init__.py-tiedostot, jotta mm. import
-lauseet toimivat halutulla tavalla. Lisää aiheesta voi lukea Pythonin dokumentaatiosta ja mallia voi ottaa referenssisovelluksesta.
Harjoitustyö 2: Toiminnallisuuden toteutus
Toteuta ainakin osa jostain edellisellä viikolla tekemäsi määrittelydokumentin toiminnallisuudesta. Pelkät tyhjät luokat tai funktiot ilman toiminnallisuutta eivät tuo pisteitä.
Toteutukseen liittyviä ohjeita löydät täältä. Jos olet toteuttamassa peliä, kannattaa yleisten ohjeiden lisäksi tutustua pygame-ohjeeseen.
Harjoitustyö 3: Testaamisen aloittaminen
Sovelluksella on oltava vähintään yksi testi. Testin tulee olla mielekäs, eli sen on testattava jotain ohjelman kannalta merkityksellistä asiaa. Testin tulee myös mennä läpi. Lisää testejä varten src hakemistoon hakemisto tests ja lisää testitiedostot sinne:
src/
tests/
__init__.py
...
...
Kertaa edellisen viikon unittest-ohjeet, jos tämä tuottaa hankaluuksia.
Harjoitustyö 4: Testikattavuusraportti
Ohjelmalle tulee pystyä generoimaan coverage-työkalun avulla testikattavuusraportti. Projektin juurihakemistossa (samassa hakemistossa, missä pyproject.toml-tiedosto sijaitsee) tulee olla .coveragerc-tiedosto, jossa määritellään, mistä hakemistosta testikattavuus kerätään. Testeihin liittyvä koodi tulee jättää testikattavuusraportin ulkopuolelle:
[run]
source = src
omit = src/**/__init__.py,src/tests/**
Kertaa edellisen viikon coverage-ohjeet, jos tämä tuottaa hankaluuksia. Mallia coveragen konfigurointiin voi tarvittaessa ottaa referenssisovelluksesta.
Harjoitustyö 5: Invoke-tehtävät
Toteuta projektille seuraavat Invoke-tehtävät:
poetry run invoke start
käynnistää ohjelmanpoetry run invoke test
suorittaa testit pytestin avullapoetry run invoke coverage-report
kerää coveragen avulla testikattavuuden ja muodostaa sen perusteella selaimessa avattavan, HTML-muotoisen testikattavuusraportin
Mallia Invoke-tehtävien toteutukseen voi ottaa tarvittaessa referenssisovelluksesta. Voit halutessasi lisätä myös muita tehtäviä, joita koet projektisi kannalta hyödylliseksi.
Harjoitustyö 6: Changelog
Changelogin ylläpitäminen on yleinen tapa dokumentoida merkittävät muutokset, joita ohjelmistoprojektissa tapahtuu sen kehityksen edetessä. Lisää projektin dokumentaatio-hakemistoon tiedosto changelog.md ja dokumentoi siihen jokaisen viikon aikana tapahtuneet merkittävät muutokset. Merkittäviä muutoksia ovat esimerkiksi uudet käyttäjälle näkyvät toiminnallisuudet, suuremmat arkkitehtuuriset muutokset (esimerkiksi uudet luokat ja niiden vastuualueet) ja uudet testauksen kohteet. Esimerkiksi referenssisovelluksessa tämän viikon changelog-merkintä on seuraava:
## Viikko 3
- Käyttäjä näkee listan kaikista tehtävistä
- Lisätty TodoRepository-luokka, joka vastaa tehtävien tallennuksesta CSV-tiedostoon
- Lisätty TodoService-luokka, joka vastaa sovelluslogiikan koodista
- Testattu, että TodoRepository-luokka palauttaa kaikki tehtävät
Lisää README.md-tiedostoon linkki, joka vie lisäämääsi changelog.md-tiedostoon.
Harjoitustyö 7: Muuta
Varmista vielä, että seuraavat asiat ovat kunnossa:
- Tuntikirjanpito on ajantasalla
- Tuntikirjanpitoon ei merkitä laskareihin käytettyä aikaa
- Viikolle on tehty changelog-merkintä changelog.md-tiedostoon
- Repositorion README.md-tiedosto kunnossa
- Tiedosto on kurssin tämän vaiheen osalta relevantin sisällön suhteen samankaltainen kuin referenssisovelluksen README.md-tiedosto
- Kaikki ylimääräinen, mm. linkit laskareihin on poistettu
- Repositorio on siisti
- Ei ylimääräistä tavaraa (esim.
pytest
-, taicoverage
-komentojen generoimia hakemistoja ja tiedostoja) - Laskarit jätetään hakemiston laskarit alle
- Järkevä .gitignore-tiedosto olemassa. Mallia voi ottaa referenssisovelluksesta
- Ei ylimääräistä tavaraa (esim.
Harjoitustyön toimivuus
HUOM: Saadaksesi harjoitustyöstä viikkokohtaiset pisteet, sovelluksen tulee toimia laitoksen tietokoneella ja ohjaajien pitää pystyä se niiltä aukaisemaan! Voit testata tätä millä tahansa Cubbli-tietokoneella, kuten fuksiläppärillä, tai laitoksen tietokoneluokkien tietokoneilla. Testaus onnistuu myös virtuaalityöasemassa joko selaimen tai VMWare Horizon -asiakasohjelman avulla.
Virtuaalityöasemassa oman sovelluksen testaaminen onnistuu selaimen avulla seuraavasti:
- Kirjaudu virtuaalityöasemaan ja valitse Cubbli Linux
- Käynnistä terminaali ja tarkista käytössäoleva Python-versio komennolla
python3 --version
. Jos versio on alle 3.8, päivitä versio tämän ohjeen avulla - Varmista, että Poetry on asennettu suorittamalla komento
poetry --version
. Jos asennus puuttuu, seuraa näitä Linux-asennuksen ohjeita - Kloonaa repositoriosi haluamaasi hakemistoon
git clone
-komennolla - Siirry repositoriosi hakemistoon ja asenna riippuvuudet komennolla
poetry install
. Huomaa, että komento tulee suorittaa hakemistossa, jossa pyproject.toml-tiedosto sijaitsee
Mikäli yhteys virtuaalityöasemaan pätkii, kannattaa kokeilla toista selainta. Käyttäjät ovat raportoineet ainakin Google Chromen toimivan varsin hyvin. Myös VMWare Horizon Clientin asentaminen saattaa auttaa.
HUOM: Jos suoritat SQLite-tietokantaa käyttävää sovellusta virtuaalityöasemassa, saatat törmätä virheeseen database is locked
. Ongelma ratkeaa luultavasti tämän ohjeen avulla.
Älä plagioi tai riko tekijänoikeuksia
Plagiointi
Kurssilla seurataan Helsingin yliopiston opintokäytäntöjä. Plagiarismi ja opintovilppi, eli esimerkiksi netissä olevien tai kaverilta saatujen vastausten kopiointi ja niiden palauttaminen omana työnä on kiellettyä. Todettu opintovilppi johtaa kurssisuorituksen hylkäämiseen ja toistuva opintovilppi voi johtaa opinto-oikeuden määräaikaiseen menettämiseen.
Mitä plagiointi tarkoittaa harjoitustyön yhteydessä? Koodin suora kopioiminen on kiellettyä poikkeuksena muutaman rivin mittaiset algoritmit ja ChatGPT:n tai vastaavien välineiden generoima koodi (ks. alla). Myös koodin rakenteen suora kopioiminen esim. siten että muuttujien ja funktioiden nimet muutetaan lasketaan plagioinniksi. Toisaalta esim. verkosta löytyneitä kuvia saa käyttää, jos tähän on oikeus (ks. kohta tekijänoikeudet), mutta näin tehtävessä tulee työn dokumentaatioon tehdä “lähdeviite”, eli mainita mistä lainaus on tehty.
Samat plagiaattisäännöt koskevat työn dokumentaatiota ja erityisen kiellettyä on copy pasteta referenssisovelluksen dokumentaatiota.
ChatGPT ja vastaavat
Myös ChatGPT:n ja vastaavien tekoälyyn perustuvien välineiden (kuten esim. Microsoft Copilotin, Google Geminin, CurreChatin tai GitHub Copilotin) generoiman koodin tai tekstin esittäminen itse kirjoitetuksi on plagiointia. Generoidun koodin käyttö on kurssilla sallittua, mutta “ympäröi” aina tällainen koodi kommenteilla # generoitu koodi alkaa ja # generoitu koodi päättyy. Tee näin myös silloin, jos olet tehnyt generoituun koodiin vain vähäisiä muutoksia (vaihtanut muuttujien ja funktioiden nimiä tms.). Toisaalta, jos tämä ei käyttötavasta johtuen tunnu mielekkäältä, voit vaihtoehtoisesti liittää dokumentaatioon kuvauksen siitä, että millä tavoin ja missä laajuudessa olet tekoälypohjaisia välineitä harjoitustyösi missäkin osassa käyttänyt.
Muistathan, että ChatGPT:n ja vastaavien välineiden käyttö yksikkötestien koodin generointiin on kurssilla kiellettyä (muuten saa kyllä käyttää testailtaessakin).
Tekijänoikeudet
Kunnioita muutenkin tekijänoikeuksia ja muita immateriaalioikeuksia. Muista, että et saa käyttää mitä tahansa verkosta löytynyttä omassa työssäsi. Tämä koskee monenlaista materiaalia ohjelmakoodista kuviin ja teksteihin. Tarkista siis aina, että onko käyttö sallittua sen lisenssin mukaan, jolla materiaali mahdollisesti on jaettu. Muista, että harjoitustyöt ovat lähtökohtaisesti julkisia GitHubissa. On omalla vastuullasi, ettet riko tekijänoikeuksia.