Testing

For å finne ut om koden man skriver (og endrer på senere) fungerer er det svært vanlig å skrive tester. Heldigvis er det svært enkelt å komme i gang med dette i Python.


Eksempel hvor flere enkle metoder testes:

import unittest
from funksjoner import pluss, minus, lagFormattertNavn

class FunksjonerTest(unittest.TestCase):

   def testPluss(self):
      self.assertEqual(pluss(2,2), 4)

   def testMinus(self):
      self.assertEqual(minus(2,2), 0)

   def testLagFormattertNavn(self):
      self.assertEqual(lagFormattertNavn("oVe", "baKkeN"), "Ove Bakken")

unittest.main()
# gir:
# ...
# -----------------------
# Ran 3 tests in 0.000s
# OK

Metodene som testes (fra filen funksjoner.py som importeres over):

def pluss(tall1, tall2):
   return tall1+tall2

def minus(tall1, tall2):
   return tall1-tall2

def lagFormattertNavn(fornavn, etternavn):
   navn = fornavn.strip() + ' ' + etternavn.strip()
   return navn.title()

Eksempel hvor en klasse testes:

import unittest
from handlekurv import Handlekurv

class TestHandlekurv(unittest.TestCase):

   def setUp(selv):
      selv.handlekurv = Handlekurv()

   def test(selv):
      selv.handlekurv.plasser("bananer")
      selv.handlekurv.plasser("paprika")
      selv.handlekurv.plasser("smurfedrops")
      selv.assertIn("bananer", selv.handlekurv.innhold)
      selv.assertTrue( selv.handlekurv.inneholder("bananer") )
      selv.assertEqual( selv.handlekurv.vis(), "bananer paprika smurfedrops ")
      selv.handlekurv.fjern("bananer")
      selv.assertNotIn("bananer", selv.handlekurv.innhold)

unittest.main()
# gir:
# .
# ---------------------------
# Ran 1 test in 0.000s
# OK

Først opprettes en tom handlekurv i setUp, så legges det tre stk. varer i denne.

Så tester man direkte på listen i handlekurvobjektet for å se at ene varen faktisk ligger der som den skal. Deretter tester man metoden inneholder i handlekurvklassen for å se at denne gir samme svaret.

Etterpå tester man metoden vis som også er del av handlekurvklassen, hvor man sjekker den tekstlige representasjonen av handlekurvens innhold.

Til slutt fjerner man en vare og tester for å se at denne forsvinner.

Klassen som testes (fra filen handlekurv.py som importeres over):

class Handlekurv():

   def __init__(selv):
      selv.innhold = []

   def plasser(selv, vare):
      selv.innhold.append(vare)

   def inneholder(selv, vare):
      return vare in selv.innhold

   def fjern(selv, vare):
      selv.innhold.remove(vare)

   def vis(selv):
      oversikt = ""
      for vare in selv.innhold:
         oversikt += vare + " "
      return oversikt

For å holde på varene brukes det en liste som oppbevarer tekst.

Unntak og feil

Ofte oppstår det unntak («exception») eller feil («error») i et program.

Disse må håndteres riktig hvis man vil være sikker på at programmet ikke avslutter brått og ødelegger for brukeren.


Et enkelt eksempel:

resultat = 10/0
# gir: 'ZeroDivisionError: division by zero'

Her forsøker man å dele på null, da vil programmet avslutte brått.


I stedet må man fange opp problemet – et eksempel:

try:
   resultat = 10/0
except ZeroDivisionError:
   print("Dele på null er tull!")
   # skriver ut 'Dele på null er tull!' i terminalen

Her får man i stedet en beskjed om at deling på null er tull. Programmet kan derfor fortsette å kjøre, man kan be om en ny verdi fra bruker o.s.v..


I tillegg til å fange opp problemet, kan man også ha funksjonalitet som kjører kun hvis operasjonen gikk bra.

Eksempel:

try:
   gjørViktigArbeid() # utfører noe meget kritisk
except Error:
   rullTilbake() # det gikk i dass så vi fjerner alle endringer
else:
   lagre() # Puuh, det gikk bra så vi lagrer alle endringer

Her har man løsninger for både når ting går som planlagt, og når de ikke gjør det.

Filer

Både skriving til fil og lesing fra fil er enkelt i Python.


Eksempel #1 på lesing fra fil:

with open("inndata.txt") as filobjekt:
   innhold = filobjekt.read()
   # variabelen innhold har nå alt innhold fra filen inndata.txt

Problemet her er at man ofte ikke vil ha alt på en gang. F.eks. kan filer være store og derfor forårsake problemer hvis innholdet tar opp veldig mye plass.

Eksempel #2 på lesing fra fil:

with open("inndata.txt") as fileobjekt:
   for linje in fileobjekt:
   # gir en og en linje fra inndata.txt

Her derimot får man bare èn linje om gangen som man så kan gjøre noe med.


Å skrive til en fil er også ganske enkelt.

Eksempel på (over)skriving:

with open("utdata.txt", "w") as filobjekt:
   filobjekt.write("Dette er en test!")
   # her skrives "Dette er en test!" til
   # filen utdata.txt som opprettes eller erstattes

Eksempel på tillegging:

with open("utdata.txt", "a") as filobjekt:
   filobjekt.write("\n")
   filobjekt.write("Dette er en test!")
   # her lages ny linje også skrives "Dette er en test!"
   # på slutten av filen utdata.txt

Den store forskjellen er i grunn a (for «append») i stedet for w (for «write»).


For direkte skriving og lesing av objekter brukes følgende:

import json

with open("objekt.json", "w") as filobjekt:
   json.dump(objekt, filobjekt)
import json

with open("objekt.json") as filobjekt:
   objekt = json.load(filobjekt)

Hvor objektet f.eks. kan se slik ut:

objekt = {
   "navn": "Ove Bakken",
   "epost": "post@ovebakken.no"
}

Dette objektet er uttrykt ved hjelp av JSON (som står for «JavaScript Object Notation»), men også lister og tupler kan behandles.

Klassearv

Når man lager klasser er ofte oppdeling i superklasser og underklasser å foretrekke fordi dette gjør logikken klarere.


Et eksempel på en ganske generell klasse:

class Motorsykkel():

   def __init__(selv, navn, kubikk, årsmodell, farge, produsent):
      selv.navn = navn
      selv.kubikk = kubikk
      selv.årsmodell = årsmodell
      selv.farge = farge
      selv.produsent = produsent

   def hentBeskrivelse(selv):
      return selv.navn + " " + str(selv.kubikk) + " " + str(selv.årsmodell) + ", produsert av " + selv.produsent + ", " + selv.farge

Denne klassen gjør ikke så mye enda, det eneste som foregår er oppbevaring av data som også kan hentes ut igjen i form av en beskrivelse.


Eksempel på bruk av klassen over:

motorsykkel = Motorsykkel("GSX F", 600, 1995, "svart og oransje", "Suzuki")

motorsykkel.hentBeskrivelse()
# gir:
# "GSX F 600 1995, produsert av Suzuki, svart og oransje"

Her opprettes et objekt av typen Motorsykkel som gis en rekke data.

Etterpå hentes beskrivelsen.


Eksempel på en underklasse som arver fra klassen øverst, hvor denne dermed blir til en superklasse:

class TohjulsMotorsykkel(Motorsykkel):

   def __init__(selv, navn, kubikk, årsmodell, farge, produsent, type):
      super().__init__(navn, kubikk, årsmodell, farge, produsent)
      selv.type = type

   def hentBeskrivelse(selv):
      return super().hentBeskrivelse() + ", type " + selv.type

Her tar man i mot de samme dataene som i superklassen, hvor disse videresendes for å klargjøre denne delen først.

Til slutt angir man en ny definisjon av metoden hentBeskrivelse så denne gir en mer utfyllende beskrivelse.


Eksempel på bruk av underklassen over:

minMotorsykkel = TohjulsMotorsykkel("GSX F", 600, 1995, "svart og oransje", "Suzuki", "sportstouring")

minMotorsykkel.hentBeskrivelse()
# gir:
# "GSX F 600 1995, produsert av Suzuki, svart og oransje, type sportstouring"

Her inneholder beskrivelsen det nye feltet type.

Python-klasser

I likhet med andre objektorienterte språk har også Python klasser.

Ei klasse er som en arkitekttegning, mens det man bygger med utgangspunkt i tegningen er objektet. Èn klasse kan dog gi flere objekter, ikke bare ett.


Eksempel på klasse:

class Handlekurv():

   def __init__(selv, kundenavn):
      selv.kundenavn = kundenavn
      selv.varer = [] # tom liste som lar seg endre

   def leggTilVare(selv, vare):
      selv.varer.append(vare)

   def fjernVare(selv, vare):
      selv.varer.remove(vare)

   def hentOversikt(selv):
      if len(selv.varer) > 0:
         oversikt = "Handlekurven til " + selv.kundenavn + ":"
         for vare in selv.varer:
            oversikt += "\n"
            oversikt += vare
         return oversikt
      else:
         return "Ingen varer i handlekurven til " + selv.kundenavn

Klasse for handlekurv hvor man både kan legge til og fjerne varer, samt hente ut en komplett innholdsbeskrivelse med navn på kunden.


Eksempel på bruk av klassen over hvor man oppretter en handlekurv:

handlekurv = Handlekurv("Ove Bakken")
handlekurv.hentOversikt()
# gir: "Ingen varer i handlekurven til Ove Bakken"

handlekurv.leggTilVare("Sokker")
handlekurv.hentOversikt()
# gir:
# "Handlekurven til Ove Bakken:"
# "Sokker"

handlekurv.fjernVare("Sokker")
handlekurv.hentOversikt()
# gir: "Ingen varer i handlekurven til Ove Bakken"

Først opprettes handlekurven hvor det gis navn på kunden. Så testes det for å se om den legger til og fjerner varer riktig.

Moduler

Ofte har man mye kode som bør splittes ut for seg selv. Denne kan man da legge i en modul som importeres ved senere anledninger.

Rent praktisk blir dette en egen fil som man refererer til i kildekoden når man ønsker å bruke funksjonalitet fra den.


Eksempel på modul:

def pluss(tall1, tall2):
   return tall1+tall2

def minus(tall1, tall2):
   return tall1-tall2

def gange(tall1, tall2):
   return tall1*tall2

def dele(tall1, tall2):
   return tall1/tall2

def eksponent(tall1, tall2):
   return tall1**tall2

Dette er en modul for regnefunksjoner med filnavn matte.py.


Eksempel #1 på bruk av modulen over:

import matte

matte.pluss(1,2) # gir 3
matte.minus(1,2) # gir -1
matte.gange(2,2) # gir 4
matte.dele(10,5) # gir 2.0
matte.eksponent(2,3) # gir 8

Her må man angi matte. fremfor hvert funksjonskall.


Eksempel #2 på bruk av modulen øverst:

import matte as m

m.pluss(1,2) # gir 3
m.minus(1,2) # gir -1
m.gange(2,2) # gir 4
m.dele(10,5) # gir 2.0
m.eksponent(2,3) # gir 8

Nå er matte. forkortet til m. fordi modulen fikk m som kallenavn ved import.


Eksempel #3 på bruk av modulen øverst:

from matte import pluss, minus, gange, dele, eksponent
# eller "from matte import *" men da importeres rubbel og bit (!)

pluss(1,2) # gir 3
minus(1,2) # gir -1
gange(2,2) # gir 4
dele(10,5) # gir 2.0
eksponent(2,3) # gir 8

Her kan man bruke funksjonene direkte uten å skrive noe som helst fremfor.

Ulempen er at det blir kluss om man har egne funksjoner med samme navn.


Eksempel #4 på bruk av modulen øverst:

from matte import pluss as p, minus as m, gange as g, dele as d, eksponent as e

p(1,2) # gir 3
m(1,2) # gir -1
g(2,2) # gir 4
d(10,5) # gir 2.0
e(2,3) # gir 8

Her er funksjonene gitt kallenavn i stedet for. Også her må man passe på at man ikke har egne funksjoner med samme navn.


Her har eksemplene vært med funksjoner, men det er samme greia med klasser.

(OBS: Eksemplene som importerer modulen øverst ligger i egne .py-filer som må ligge sammen med matte.py for at de skal fungere. Dette er lite optimalt for større prosjekter, da må man heller forme det som Python-pakker … )

Assosiativ tabell

Det høres kanskje vanskelig ut, men å bruke en assosiativ tabell er enkelt.

Først må man opprette den, hvor innholdet kan være hva som helst:

forfatter = {"fornavn":"Ove", "etternavn":"Bakken", "epost":"post@ovebakken.no"}
print(forfatter)
# gir: {"fornavn": "Ove", "etternavn": "Bakken", "epost": "post@ovebakken.no"}

Noen funksjoner for å endre eller slette innholdet:

drømmedama = {"fornavn":"Tone", "etternavn":"Damli", "epost":""}
del drømmedama["epost"] # sletter epost, den har jo en tom verdi så
drømmedama["etternavn"] = "Damli Aaberge" # liker hennes forrige fulle navn jeg
print(drømmedama)
# {"fornavn": "Tone", "etternavn": "Damli Aaberge"}

drømmedama.clear() # sletter alt innholdet fordi jeg har ombestemt meg
print(drømmedama) # gir: {}
del drømmedama # jeg sletter hele greia fordi jeg ikke klarer å bestemme meg
drømmedama = {"fornavn":"Jenny", "etternavn":"Skavlan"} # jeg har bestemt meg!
print(drømmedama) # gir: {"fornavn": "Jenny", "etternavn": "Skavlan"}

En assosiativ tabell kan selvsagt inneholde andre tabeller igjen:

telefonkatalogen = {"personer":[]} # en tom katalog fordi listen er tom
telefonkatalogen["personer"].append(forfatter)
telefonkatalogen["personer"].append(drømmedama)
print(telefonkatalogen)
# gir: {"personer": [{"fornavn": "Ove", "etternavn": "Bakken", "epost": "post@ovebakken.no"}, {"fornavn": "Jenny", "etternavn": "Skavlan"}]}

Funksjoner

Det er særdeles enkelt å komme i gang med funksjoner i Python:

def skrivHalloVerden():
   print("Hallo verden!")

skrivHalloVerden() # skriver ut "Hallo verden!"

def halloVerden():
   return "Hallo verden!"

halloVerden() # gir: "Hallo verden!"

For litt enkel matte:

def plussEn(tall):
   return tall+1

plussEn(2) # gir: "3"

def pluss(tall1, tall2):
   return tall1 + tall2

pluss(2,2) # gir: "4"

For inndata med satte standardverdier som dermed er frivillige å oppgi:

def skrivMelding(melding="Velkommen!"):
   print(melding)

skrivMelding() # skriver ut "Velkommen!" fordi ingen melding er gitt
skrivMelding("Ha en god dag!") # skriver ut "Ha en god dag!"

def skrivLogg(dag, innhold, tilfredsstillelse=""): # tilfredsstillelse er valgfritt å oppgi
   print("Logg for " + dag + ":")
   if (tilfredsstillelse):
      print(innhold + " (" + tilfredsstillelse + ")")
   else:
      print(innhold)

skrivLogg("idag", "Jeg har skrevet Python", "5/6")
# gir:
# "Logg for idag:"
# "Jeg har skrevet Python (5/6)"
skrivLogg("igår", "Jeg var på butikken en tur")
# gir:
# "Logg for igår:"
# "Jeg var på butikken en tur"

For endring av rekkefølge på innndata:

def skrivHandleliste(mat, butikk):
   print("Kjøp " + mat + " på " + butikk)

skrivHandleliste("Paprika", "Kiwi") # skriver "Kjøp Paprika på Kiwi" skrivHandleliste(mat="Egg", butikk="Coop Extra") # skriver "Kjøp Egg på Coop Extra" 
skrivHandleliste(butikk="Rema1000", mat="Epler") # skriver "Kjøp Epler på Rema1000"

For ukjent antall inndataverdier:

def lagPizzaMedTilbehør(*tilbehør):
   print("Lager pizza med:")
   for ekstra in tilbehør:
      print("-" + ekstra)

lagPizzaMedTilbehør("tomater", "pepperoni", "løk", "mais")
# gir:
# "Lager pizza med:"
# "-tomater"
# "-pepperoni"
# "-løk"
# "-mais"

Retur av objekt:

def navnSomObjekt(fornavn, etternavn):
   return {"fornavn":fornavn, "etternavn":etternavn}

navnSomObjekt("Ove", "Bakken") # gir: {"fornavn": "Ove", "etternavn": "Bakken"}

if, while, for og ternary

For å styre kjøringen av et program trenger man å gjøre logiske sjekker for å bestemme hva man skal gjøre i en gitt situasjon.

if

Brukes for å sjekke med en gang om noe er sånn det skal være:

if True:          # Sann
   print("Denne linjen vil bli vist.")

if True and True: # Sann og sann = sann
   print("Denne linjen blir også vist.")

if True or False: # Sann eller usann = sann
   print("Og denne.")

if False:             # Usann
   print("Ikke denne.")

if True and False:    # Sann og usann = usann
   print("Og ikke denne.")

if False and False:   # Usann og usann = usann
   print("Ej heller denne.")

if not True:          # Ikke sann = usann
   print("Eller denne.")

if not not False:     # Ikke ikke usann = usann
   print("Eller denne.")

if not True or False: # Ikke sann eller usann = usann
   print("Eller denne")

if True:   # Sann
   print("Men så vises denne")
else:      # Nødt til å være usann
   print("Vises ikke")

if 1 == 2:    # Usann
   print("Ikke denne heller")
elif 1 == 1:  # Sann
   print("Denne vises")
elif 1 == 3:  # Usann
   print("Denne vises ikke")
else:         # Vises ikke fordi første elif over er sann
   print("Vises ikke")

while

For å gjøre noe igjen og igjen helt til noe bestemt skjer:

while True: # Vil kjøre i det uendelige!
   print("Vises igjen og igjen.")

nummer = 0
while nummer < 10:
   print(nummer, end=" ")
   nummer += 1
# gir: "0 1 2 3 4 5 6 7 8 9" 

nummer = 0
while nummer < 10:
   print(nummer, end=" ")
   nummer += 2
# gir: "0 2 4 6 8"

nummer = 10
while nummer > 0:
   print(nummer, end=" ")
   nummer -= 2
# gir: "10 8 6 4 2"

for

Om man f.eks. trenger å kjøre gjennom en liste fordi man skal sjekke eller gjøre noe med innholdet i denne:

for nummer in [3,4,5,6,7,8,9]:
   print(nummer, end=" ")
# gir: "3 4 5 6 7 8 9"

for nummer in range(0,10):
   print(nummer, end=" ")
# gir: "0 1 2 3 4 5 6 7 8 9"

for nummer in range(10,20):
   print(nummer, end=" ")
# gir: "10 11 12 13 14 15 16 17 18 19"

for nummer in range(0,10,2):
   print(nummer, end=" ")
# gir: "0 2 4 6 8"

for nummer in range(10,0,-1):
   print(nummer, end=" ")
# gir: "10 9 8 7 6 5 4 3 2 1" 

for nummer in range(10,0,-2):
   print(nummer, end=" ")
# gir: "10 8 6 4 2"

ternary

Brukt for omtrent samme situasjoner som if-else:

temperatur = 10
print("Det er varmt") if temperatur > 20 else print("Det er kaldt")
# gir: "Det er kaldt"

temp = 0
print("Varmt") if temp > 0 else ( print("Kaldt") if temp < 0 else print("0") )
# gir: "0"

Denne måten å sjekke på er av mange mislikt fordi den ser så annerledes ut sammenlignet med tilsvarende i andre programmeringsspråk, som f.eks. Java:

# ...
temp > 0 ? print("Varmt") : ( temp < 0 ? print("Kaldt") : print("0") );

switch

Denne finnes ikke direkte i Python (enda).


Hva som er det riktige for deg når du skal bruke en eller flere av disse må du selv bestemme; det finnes ingen fasitsvar på hva som er riktig eller galt.

Fokuser heller på å gjøre det enkelt og lesbart.