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.