Föreläsningsöversikt¶
- Information om datorsalstentan
- Laboration 7
- Kort om algoritmer och komplexitet
- Skriva till fil
- Ännu mer om testning av kod
- Repetition
Datorsalstentan¶
logistik på plats, upplägg, inlämning
Datorsalstenta, 3 juni kl. 8.00-12.00¶
- Anmälan via Lisam eller LiU-appen.
- Anmälningsperiod: 4 maj-24 maj
- Info på kurshemsidan under "Datorsalstenta"
- Hybridrättning
- Kort allmän info
Sal, inloggning, kontroll av bok¶
- Information om vilken sal du ska gå till vid SU17/18
- Inloggning
- Står "tentainloggning"
- Logga in med LiU-ID och ditt vanliga lösenord
- Liknande datormiljö som vid labbande, men utan internetåtkomst
- Kontroll av bok
Filer: tenta, datafiler, referens-PDF¶
- Din hemkatalog under tentan nås via
~/
- (Inte din vanliga hemkatalog, den har du inte tillgång till under tentan.)
- Tentan som PDF samt eventuella filer som hör ihop med tentan finns i katalogen
~/given_files
- Här finns också en PDF med dokumentationen för Python 3.10.
~/given_files
är skrivskyddad
Skriva tentan - rekommendation¶
- Kopiera ev. datafiler från
given_files
till skrivbordet. - Spara dina filer till skrivbordet.
- Skrivbordet via terminal:
cd ~/Desktop
- OBS! Glöm inte att spara dina filer, VSCode kommer inte spara automatiskt under tentan.
- Du har inte heller tillgång till VSCodes Python extension under tentan, dvs. ingen code completion/IntelliSense och ingen "Play"-knapp för att köra din kod, du måste kunna köra via terminalen.
Frågor under tentan¶
- Praktiska frågor: jour-assistenter och tentavakter
- Frågor om tentauppgifter: använd studentklienten
- Studentklienten: program som startas automatiskt vid inloggning och används för kommunikation med examinator samt inlämning av uppgifter
Tentaupplägg¶
- Två delar, Del 1 och Del 2
- Del 1: grundläggande uppgifter, samma storlek som enskilda uppgifter från Pythonuppgifter 1-3
- Del 2: något mer omfattande uppgifter
- Inga avdrag för avvikelser från PEP8 eller PEP257
Del 1¶
- Krävs för att bli godkänd på tentan
- Rättas live under tentatillfället och uppgifterna kan kompletteras i mån av tid.
- Del 1 innehåller 5 uppgifter: 1.1, 1.2, etc. eventuellt med mer än en deluppgift.
- Kom ihåg att lämna in en uppgift så fort ni är klara med den.
- Om ni skickar in alla uppgifter 5 minuter innan tentans slut hinner ni inte få svar eller möjlighet att komplettera.
- För att gå godkänt på Del 1 gäller att alla uppgifter måste vara godkända.
- Målsättningen är att alla ska veta huruvida de blivit åtminstone godkända på tentan vid tentatidens slut.
- (Några få gränsfall kan dock komma att godkännas i efterhand.)
Del 2¶
- Rättas bara om Del 1 är godkänd och rättas efter tentan.
- Består av några få större problemlösningsuppgifter.
- En fungerande lösning på en uppgift ger minst 3 poäng.
- Bonuspoäng upp till max 5 poäng per uppgift delas ut för bra lösningar.
- Inkompletta och delvis felaktiga lösningar KAN ge 1-2 poäng.
- Man kommer inte veta om man uppnått kraven för 4 eller 5 vid tentatidens slut.
Del 2 - Vad menas med "bra" lösning?¶
- Lösningen är uppdelad i lämpliga delfunktioner med tydliga namn.
- Lösningen använder tydliga variabelnamn.
- Lösningen använder lämpliga datatyper.
- Lösningen innehåller inte onödiga operationer, t.ex.
- Duplicering av samma operationer på flera ställen.
- Överflödiga typkonverteringar.
- Lösningen är kommenterad på lämplig detaljnivå.
Betyg¶
- Betyg 3: Godkänd på del 1.
- Betyg 4: Godkänd på del 1 + ca 40 % på del 2.
- Betyg 5: Godkänd på del 1 + ca 70 % på del 2.
Inlämning av uppgifter¶
- En fil per uppgift
- Eventuella deluppgifter (ex. 1.2.1, 1.2.2, etc.) lämnas in i samma fil.
- Döp filen så att uppgiftsnummret finns med, t.ex. uppg12.py för ovanstående deluppgifter (står i tentan)
- Filen skickas in via studentklienten:
- Tryck på knappen "Skicka in uppgift"
- Välj vilken uppgift och välj vilken fil som skickas in
- Bekräftelse på att filen är mottagen "Begäran mottagen"
Prova-på-tenta 21 maj 10.15-11.00 och 11.15-12.00¶
- 10.15: Person 1 i varje labbpar i webreg
- 11.15: Person 2 i varje labbpar i webreg (och ev. Person 3)
- Lokal enligt vanliga schemat i TimeEdit
- Möjlighet för både er och assistenterna att förbereda er inför riktiga tentan.
- Se miljön, prova att ställa frågor i studentklienten, prova att skicka in uppgift och få feedback.
- Utnyttja den här möjligheten! Vi vet av erfarenhet att många blir stressade första gången de ser tentamiljön och detta är ett sätt för er att slippa den stressen vid det riktiga tentatillfället.
På kurshemsidan¶
- Exempeltenta och övningsuppgifter
- PDF som ni kommer ha tillgänglig som referens
- https://www.ida.liu.se/~TDDE44/kurslogistik/tenta/
Laboration 7¶
- Uppgiften
- Ordfrekvensdata
- Redigeringsavstånd
- Klasser
- Förberedelser: UML-diagram
- Problem som kan uppstå när man har mycket data
Laboration 7: Rättstavning¶
- "Fortsättning" på laboration 2
- Skript som använder ordfrekvensdata för att hitta okända ord i en text och föreslå alternativ till dessa ord med hjälp av redigeringsavstånd
- T.ex. hoppssn → hoppsan
- Rapport om okända ord + förslag sparas till fil
Laboration 7: filer¶
- ordfrekvensfiler
- innehåller antal förekomster av olika ord från diverse källor
- mest förekommande först i filerna
- några textfiler
- exempel på texter som ni kan "rättstava"
- använd gärna egna texter
Kortaste redigeringsavståndet¶
- Algoritm som räknar ut antal redigeringsoperationer som behövs för $word_A$ → $word_B$
- Levenshtein-avstånd: minsta antalet operationer av typerna lägg till bokstav, ta bort bokstav och ersätt bokstav som förändrar ett ord till ett annat.
'katt'
→'hatt'
; en operation (ersätt'k'
med'h'
)'katt'
→'kalas'
; tre operationer (ersätt första't'
med'l'
, ersätt andra't'
med'a'
, lägg till's'
)'hall'
→'hal'
; en operation (ta bort ett'l'
)
- Funktionen
minimum_edit_distance(s1, s2)
finns implementerad och redo att importera från filenmed.py
Laboration 7: Klasser¶
- Rita UML-diagram och skriv ert program utifrån det. Visa för labbassistent innan ni börjar koda.
- Uppdatera UML-diagrammet under tiden ni programmerar. Visa slutgiltig version vid redovisning.
- Minst följande två klasser:
SpellingWarning
: innehåller information om upptäckta ev. stavfel (som t.ex. plats, ordet och förslag)Report
: innehållerSpellingWarning
-objekt
- För varje upptäckt fel skapar ni en instans av klassen
SpellingWarning
. - Utöver dessa klasser är det upp till er.
Förberedelser innan ni börjar implementera¶
- Läs igenom instruktionerna på kurshemsidan
- Diskutera med er labpartner och rita upp UML-diagram.
- Visa upp ert UML-diagram och berätta hur ni tänkt för en assistent.
- Tidigare år har många hoppat över det här steget och fått flera jobbiga kompletteringar när deras approach visat sig orimlig, undvik att göra samma misstag.
Tips¶
- En rapport har många upptäckta fel → jmf. flera hus i en stad
- Vid kontroll av flera filer → skapa flera rapporter (instanser)
- Behövs flera klasser? Vad vill ni representera? När ska man använda en inbyggd datatyp, t.ex.
list
ellerdictionary
och när ska man skapa en klass? - Försök att se till så att en klass bara har ett ansvarsområde.
- Kom ihåg att använda metoder för att interagera med datan som klassen representerar, bryt inte inkapslingen i onödan.
Konsekvenser av öka mängden data att bearbeta¶
Laboration 7¶
- Körtid < 1 minut för minsta datafilen på en dator i labbsalen.
- Går att köra <1 minut för största datafilen om man tillämpar vissa (rimliga) begränsningar.
Exempeluppgift¶
text.txt
innehåller en text på 1000 ordordfrekvenser.tsv
innehåller ordfrekvenser för 500k ord;- varje rad består av
- ett ord
- ett tab-tecken (
'\t'
) - frekvensen som ordet förekommer i en viss text-korpus (samling av texter)
- Vi ska skriva en algoritm som tar fram vilka ord i
text.txt
som finns med iordfrekvenser.tsv
Loopar: Se upp för att göra om saker i onödan. Alt 1¶
- Kontrollera ett ord. För varje
word_to_check
i texten som ska kontrolleras (1 000 ord)
- 1.1 Läs in data. läs in alla ordfrekevenser och spara dem som tupler av ord och frekvens i listan
freq_data
(500 000 ord) - 1.2 Finns ord med i data? Gå igenom alla inlästa ord och kontrollera om det är
word_to_check
(500 000 ord)
- Kontrollera ett ord. För varje
- $\sim 1\ 000 \times (500\ 000 + 500\ 000) = 1 \times 10^9$ operationer
- Om en operation tar 1 nanosekund ($1 \times 10^{-9}$) tar vår algoritm 1 sekund.
Loopar: Se upp för att göra om saker i onödan. Alt 2¶
- Läs in data. Läs in alla ordfrekevenser och spara dem som tupler av ord och frekvens i seprata listor för olika längd på orden, dvs en lista per ordlängd. (500 000 ord)
- Kontrollera ett ord. För varje ord
word_to_check
i texten som ska kontrolleras (1000 ord)
- 2.1 Finns ord med i data? Gå igenom alla inlästa ord av rätt längd och kontrollera om det är
word_to_check
, avbryt så fort ordet hittats (25 000 ord, givet antagandet att vi har i snitt 50 000 ord per ordlängd och vi i snitt behöver söka igenom hälften av dessa för att hitta ordet vi söker)
- Kontrollera ett ord. För varje ord
- $\sim 500\ 000 + (1000 \times 25\ 000) = 2,55 \times 10^7$ operationer
- Om en operation tar 1 nanosekund ($1 \times 10^{-9}$) tar detta alternativ 0,0255 sekunder
Loopar: Se upp för att göra saker i onödan¶
- Alt 1:
- Läser in all ordfrekvensdata för varje ord som ska kontrolleras
- Kontrollerar
word_to_check
mot alla ord, även de som inte har samma ordlängd.
- Alt 2:
- Läser in all ordfrekvensdata en gång.
- Kontrollerar
word_to_check
mot de ord som har samma längd - Avbryter sökning när ordet har hittats
Kan vi snabba på detta ytterligare?¶
Loopar: Se upp för att göra saker i onödan¶
- Alt 1:
- Läser in all ordfrekvensdata för varje ord som ska kontrolleras
- Kontrollerar
word_to_check
mot alla ord, även de som inte har samma ordlängd.
- Alt 2:
- Läser in all ordfrekvensdata en gång.
- Kontrollerar
word_to_check
mot de ord som har samma längd - Avbryter sökning när ordet har hittats
- Vi minskade antalet ord vi kontrollerade mot.
- Kan vi minska antalet ord att kontrollera mot ytterligare?
- Kan vi minska antalet ord att kontrollera mot... till ett?
Alternativ 1: Alla ord i en bucket¶
- Vi säger att alla ord ligger i en bucket (hink) och att vi enkelt kan ta reda på vilken bucket ett visst ord måste ligga i.
- Vi måste leta igenom alla ord i rätt bucket för att vara säkra på att ett ord är okänt.
- Om alla ord ligger i samma bucket, så måste vi leta igenom alla ord.
Alternativ 2: Ord av olika längd i olika buckets¶
- Vi skapar en bucket per ordlängd (vi antar att antalet ord längre än 25 tecken är så litet att vi lägger alla dessa ord i samma bucket) och använder
len(word)
för att ta reda på vilken bucket vi ska leta igenom.
Alternativ 3: Massor med buckets¶
- Vi skapar $m$ stycken buckets.
- Vi använder någon hash-funktion $h:U \rightarrow {0, ..., m-1}$ sådan att $h(word) \in [0,m-1]$.
- Väljer vi $h$ med omsorg och gör $m$ tillräckligt stort kan vi se till att nästan varje ord har en egen bucket.
- Vi kan betrakta Alternativ 2 som ett specialfall av Alternativ 3, med ett litet $m$ och $h = $
len
.
Det verkar knöligt, måste vi göra detta själva?¶
- Nej.
- (...inte i den här kursen.)
- Pythons datatyper
dict
ochset
använder redan det här sättet att lagra data.- (...så det är lämpligt att använda dessa om och när det går.)
Komplexitet¶
Hur krävande är det att lösa en specifik uppgift med en specifik algoritm?
Vad är en algoritm?¶
- Informellt: En väldefinierad beräkningsbar (computational) procedur som givet ett värde, eller en mängd värden, som input producerar ett värde, eller en mängd värden som output. (Cormen et. al. 2009. Introduction to algorithms.)
- En algoritm sägs vara korrekt om den för varje korrekt indata, avslutar med korrekt utdata.
- Vi kan förenklat säga att allt som kan uttryckas som en funktion är en algoritm.
En algoritms komplexitet¶
- En algoritms komplexitet besvarar frågan:
Om vi ökar mängden indata, hur mycket ökar resursanvändningen?
- En algoritms komplexitet uttrycks normalt som en funktion, $f(n)$, av storleken på algoritmens indata.
- Ibland ser man $T(n)$ för tidskomplexitet och $S(n)$ för minneskomplexitet.
Olika typer av komplexitet¶
Tidskomplexitet (eng. time complexity): Hur många operationer krävs för att beräkna en algoritm?
Minneskomplexitet (eng. space complexity): Hur mycket utrymme i minnet krävs för att beräkna en algoritm?
Ibland också:
- Kommunikationskomplexitet: Hur mycket kommunikation behövs mellan parterna för att beräkna en distribuerad algoritm. (Relevant för distribuerade system, realtidssystem, HPC, etc.)
- Bitkomplexitet: En mer rigoröst definierad form av tidskomplexitet.
- Aritmetisk komplexitet: När tal är begränsade i storlek så bortser vi ofta från det faktum att olika stora tal tar olika lång tid att beräkna, när tal kan vara godtyckligt stora måste vi dock ta hänsyn till denna aritmetiska komplexitet.
Tidskomplexitet, hur många operationer krävs?¶
- Hur mycket behöver datorn göra? Hur komplicerat är det att lösa ett givet problem med en viss algoritm?
- Exempel,
for
-loop, leta efter största elementet- Hur många operationer behöver algoritmen?
In [ ]:
def get_max_value(values):
max_value = values[0] # 1
for value in values: # len(values) = n
if value > max_value: # 1
max_value = value # 1
return max_value # 1
Tidskomplexitet, hur många operationer krävs?¶
- Hur mycket behöver datorn göra? Hur komplicerat är det att lösa ett givet problem med en viss algoritm?
- Exempel,
for
-loop, leta efter största elementet- Hur många operationer behöver algoritmen? $1 + n(1 + 1) + 1 = 2n + 2$ där $n = $
len(values)
- Hur många operationer behöver algoritmen? $1 + n(1 + 1) + 1 = 2n + 2$ där $n = $
In [ ]:
def get_max_value(values):
max_value = values[0]
for value in values:
if value > max_value:
max_value = value
return max_value
Asymptotisk analys¶
- Antag att vi har funktionerna $f$ och $g$ sådana att
- Då gäller att
- Vi säger att $f$ och $g$ är asymtotiskt ekvivalenta och uttrycker detta som
Ordo-notation¶
- Eng. Big O notation
- Om det existerar några positiva heltal $M$ och $x_0$ sådana att
- så säger vi att
- eller vanligtvis bara
Ordo-reglerna¶
- Om $f(x)$ är en summa av flera termer så kan alla termer utom den med högst tillväxttakt utelämnas.
- Om $f(x)$ är en produkt av flera faktorer så kan alla konstanter (faktorer som inte beror på $x$) utelämnas.
- Alltså: Eftersom
get_max_value
tog $2n+2$ operationer att utföra så säger vi att dess tidskomplexitet är $O(n)$.- (Utläses "Ordo n" på svenska och "Order of n" på engelska.)
Exempel $O(n)$¶
In [90]:
import random
def sum_all(values):
acc = 0
for value in values:
acc += value
return acc
In [124]:
%%timeit
sum_all(random.sample(range(1_000_000), k=10))
2.98 μs ± 42.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [125]:
%%timeit
sum_all(random.sample(range(1_000_000), k=20))
5 μs ± 73.9 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [126]:
%%timeit
sum_all(random.sample(range(1_000_000), k=40))
8.17 μs ± 52.1 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [127]:
%%timeit
sum_all(random.sample(range(1_000_000), k=400))
71.3 μs ± 684 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [128]:
%%timeit
sum_all(random.sample(range(1_000_000), k=4000))
689 μs ± 4.7 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
Komplexitet, nästlade loopar¶
- Antalet par-kombinationer man kan bilda givet $n$ värden
- $\{ 1, 2, 3, ..., n \}$
- $O(n^2)$ (kvadratisk komplexitet)
In [123]:
def make_pairs(values):
# pre-allocate result list to avoid allocation artefacts
pairs = [None]*(values**2)
current_index = 0
for v1 in values:
for v2 in values:
pairs[current_index] = str(v1) + ":" + str(v2)
current_index += 1
return pairs
Exempel $O(n^2)$¶
In [131]:
def make_pairs(values):
# pre-allocate result list to avoid allocation artefacts
pairs = [None]*(len(values)**2)
current_index = 0
for v1 in values:
for v2 in values:
pairs[current_index] = str(v1) + ":" + str(v2)
current_index += 1
return pairs
In [132]:
%%timeit
make_pairs(range(10))
10.7 μs ± 120 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [133]:
%%timeit
make_pairs(range(20))
42.4 μs ± 678 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [134]:
%%timeit
make_pairs(range(40))
169 μs ± 1.05 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [135]:
%%timeit
make_pairs(range(80))
671 μs ± 6.71 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
In [136]:
%%timeit
make_pairs(range(160))
2.72 ms ± 60 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Några komplexitetsklasser i stigande ordning¶
- Sublinjär komplexitet (Woho!)
- $O(1)$ - Konstant komplexitet: T.ex. indexering i en lista, uppslagning av en variabels värde, eller att byta plats på två värden i minnet.
- $O(\log n)$ - logaritmisk komplexitet: Binärsökning (telefonkatalogsalgoritmen)
- $O(\log^k n)$ - polylogaritmisk komplexitet: Uppslagning av ett värde i en
dict
ellerset
- Subkvadratisk komplexitet
- Subexponentiell komplexitet
- Enbart teoretiskt intressant komplexitet
Några komplexitetsklasser i stigande ordning¶
- Sublinjär komplexitet
- Subkvadratisk komplexitet (Ok.)
- $O(n)$ - linjär komplexitet:
get_max_value
, summering (under antagandet att addition har konstant komplexitet) - $O(n \log^k n)$ - kvasilinjär: quicksort, merge sort, heapsort, FFT (Fast Fourier transform)
- $O(n)$ - linjär komplexitet:
- Subexponentiell komplexitet
- Enbart teoretiskt intressant komplexitet
Några komplexitetsklasser i stigande ordning¶
- Sublinjär komplexitet
- Subkvadratisk komplexitet
- Subexponentiell komplexitet (Eh...)
- $O(n^2)$ - kvadratisk: redigeringsavstånd *host* *host*
- $O(n^3)$ - kubisk: traditionell matrismultiplikation (men algoritmer så effektiva som $O(n^{2.376})$ existerar)
- $O(n^c)$ - polynomiell av grad $c$ (för någon konstant $c$): aritmetiska operationer för godtyckligt stora tal
- Enbart teoretiskt intressant komplexitet
Några komplexitetsklasser i stigande ordning¶
- Sublinjär komplexitet
- Subkvadratisk komplexitet
- Subexponentiell komplexitet
- Främst teoretiskt intressant komplexitet (Oh ****...)
- $O(2^{n^c})$ - exponentiell komplexitet: brute-force lösenordsknäckning
- $O(n!)$ - factorial complexity: situationer där alla permutationer av en sekvens måste genereras
- $O(2^{2^{p(n)}})$ - dubbelexponentiell komplexitet: typ-inferens i vissa programmeringsspråk och vissa andra fenomen inom formella språk
- $O(A(n, n))$ - Ackermann-funktionen: att beräkna hyperoperationer mha Ackermann-funktionen (se rekursion)
- $O(BB(n))$ - Busy Beaver of n, förmodligen den värsta möjliga komplexiteten (men det är en förmodan, inte en sats)
Se också https://www.bigocheatsheet.com/¶
Testning och felsöking¶
- Felsökning:
- Jag vet att något är fel med den här koden. Jag måste nu ta reda på var och varför det blir fel.
- Testning:
- Jag vet inte om programmet fungerar eller inte. Jag behöver ta reda på om alla delar fungerar som de ska.
Felsökning¶
- När vi vet att koden vi skrivit inte fungerar som den ska behöver vi t.ex. ta reda på:
- Under vilka omständigheter fungerar inte koden?
- Var i koden uppstår felet? / Vad orsakar felet?
- Verktyg vi kan använda oss av vid felsökning
- Felmeddelanden när programmet kraschar
- Spårutskrifter
- Olika debugverktyg
Testning¶
- Syftet med testning är att ta reda på om det vi testar fungerar eller inte. T.ex. om programmet uppför sig som vi förväntar oss.
- Stor bredd i det man kan referera till med "testning"
- Testning hjälpa oss påvisa förekomsten av fel.
- Rent praktiskt omöjligt att med hjälp av testning garantera frånvaron av fel, men vi kan använda testning för att minimera antalet fel!
Testning i denna kurs och utanför denna kurs¶
Testning i denna kurs¶
- Testning är en del av lärandemålen för kursen
- Ni har alla testat er kod på ett eller annat sätt
Testning inom mjukvaruutveckling¶
- Test på olika abstraktionsnivåer:
- enhetstestning - enskilda enheter, "units", av mjukvaran för sig själv, t.ex. enskilda funktioner, klasser
- integrationstest - test av specifika enheters interaktion med varandra
- systemtest - test av systemet som helhet
- Test enligt olika kriterier:
- funktionstest - test av specifik funktionalitet hos ett program, t.ex. "lägga till en uppgift"
- prestandatest - test av prestanda hos programmet, t.ex. hur lång tid tar det att öppna en fil, hur mycket minne använder programmet, etc.
- acceptanstest - test av alla krav ställda på programmet
- användbarhetstestning - test av hur användbart programmet är för en viss användarkategori
- Speciella ramverk och program finns för att underlätta testning, men användning av dessa ligger utanför kursen.
Automatiserad testning¶
- Tester som kan skrivas i kod och köras automatiskt, t.ex. varje gång man lagt till ny funktionalitet eller refaktorerat (ändrat i existerande kod)
- Ofta felaktigt använt synonymt med enhetstestning men även t.ex. integrationstest, och i viss utsträckning systemtest, brukar automatiseras.
- Även prestandatest kan ofta automatiseras när programmen ställer höga krav på prestanda, t.ex. i realtidssystem, simuleringsmjukvaror eller spel.
- Svårare att automattesta:
- grafiska gränssnitt
- funktionalitet som är beroende av komplex data som är resultatet av interaktion mellan många komponenter i ett system
- funktionalitet som är beroende av yttre faktorer
Vilka typer av test ingår i kursen?¶
- Test av funktioner, fungerar de som de ska?
funktion(indata)
→returvärde
- Enklare test av instanser av klasser, t.ex.
instans.metod()
→returvärde
instans.metod(indata)
→returvärde
- värdet hos instansvariabler efter att olika metoder körts
Vad ska man tänka på vid testning?¶
- Båda nedanstående funktioner ska addera talen
v1
, ochv2
och sedan returnera resultatet. - I nedanstående testkörning verkar båda vara korrekt implementerade?
In [11]:
def add_values1(v1, v2):
return v1 + v2
def add_values2(v1, v2):
return v1 * v2
# båda anropen returnerar 4, är båda implementationerna korrekta?
print(add_values1(2, 3))
print(add_values2(2, 3))
5 6
Vad behöver man veta för att testa sin kod?¶
Vad måste vi veta för att testa en enhet?¶
- Hur ser specifikationen ut? Dvs.
- Vilka starttillstånd och testförhållanden är giltiga?
- Indata?
- Värden på instansvariabler innan en metod körs?
- Vad ska enheten vi vill testa göra?
- Hur ser utdata ut? Vilken utdata är giltig?
- Vilken utdata ska vi förväntas oss givet en viss indata?
Testdata¶
- I de flesta fall är det omöjligt att göra en uttömmande testning, dvs testa alla möjliga fall (t.ex. alla möjliga indata).
- Vi behöver titta på giltiga indata och försöka hitta vilka kategorier av indata som finns.
- Vilka indata ger är samma returvärde?
- Vilka indata testar samma aspekt av implementationen?
- Att hitta dessa kräver ofta att man förstår hur implementationen av en funktion eller klass kan göras.
- Ett mål: med så få test som möjligt kunna täcka så många fall som möjligt.
Typfall och gränsfall¶
- Typfall (eng. typical case) är de testvärden som motsvarar den vanligaste typen av input där enheten förväntas bete sig normalt.
- Gränsfall (eng. edge case) är de testvärden som ligger nära, på eller utanför gränsen för normal användning.
- Gränsfallen kan kräva fler test för olika typer av gränser och för att testa felhantering vid olika gränsöverträdelser.
- Testning handlar till stor del om att hitta och definiera gränsfallen samt konstruera lämpliga tester för de fallen.
- Vad är några typfall och gränsfall för funktionen nedan:
In [14]:
from math import floor
def split_winnings(cash_prize_amount, number_of_winners):
return floor(cash_prize_amount / number_of_winners)
In [19]:
split_winnings(1000, '2')
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[19], line 1 ----> 1 split_winnings(1000, '2') Cell In[14], line 4, in split_winnings(cash_prize_amount, number_of_winners) 3 def split_winnings(cash_prize_amount, number_of_winners): ----> 4 return floor(cash_prize_amount / number_of_winners) TypeError: unsupported operand type(s) for /: 'int' and 'str'
Vad behöver man göra för att testa sin kod?¶
Göra sitt program lättare att testa¶
- Genom att dela upp ett program i fler funktioner/klasser/ metoder ökar man även i de flesta fall upplösningen på det som går att testa
- Funktioner som returnerar värden är lättare att testa än funktioner som inte returnerar värden.
- Genom att dokumentera sin kod finns det en specifikation som man kan använda som vägledning för att skapa rätt test.
Hur kan man testa?¶
- Att testa sitt program interaktivt kan ta tid (för att det sker manuellt), men ger stora möjligheter till att utforska resultaten.
- Att automatisera ett test, dvs skriva kod som testar funktionaliteten är snabbare och är upprepningsbart.
- Viktigt att veta vad vi ska testa!
Exempel på automatisering av test¶
- Automatisera genomförandet av testoperationen, t.ex.
- anrop av funktioner + okulär besiktning av resultaten för att validera korrekthet
- Exempel nedan där returvärden skrivs ut
In [20]:
def keep_even_values(values):
output = []
for value in values:
if value % 2 == 0:
output.append(value)
return output
print(keep_even_values([]))
print(keep_even_values([1]))
print(keep_even_values([2]))
print(keep_even_values([1, 2, 3, 4]))
[] [] [2] [2, 4]
Exempel på automatisering av test¶
- Automatiserad validering
- automatisk jämförelse av faktiskt resultat av testoperationen med förväntat resultat
- Exempel med villkorssatser på nästa bild
Exempel på automatisering av test¶
In [21]:
def keep_even_values(values):
output = []
for value in values:
if value % 2 == 0:
output.append(value)
return output
def test_keep_even_values():
if keep_even_values([]) != []:
return False
if keep_even_values([1]) != []:
return False
if keep_even_values([2]) != [2]:
return False
if keep_even_values([1, 2, 3, 4]) != [2, 4]:
return False
return True
if test_keep_even_values():
print("keep_even_values() fungerar.")
else:
print("keep_even_values() fungerar INTE.")
keep_even_values() fungerar.
Nyckelordet assert
¶
- Nyckelordet
assert
används när man vill kontrollera om ett uttryck är sant i syfte att fånga upp fall när det inte är det. - Om uttrycket är sant går Python vidare i koden.
- Om uttrycket är falskt, inträffar ett
AssertionError
som sedan kan fångas. - Man kan använda villkorssatser i samma syfte, men koden kan bli kortare och tydligare med
assert
- Syntax:
assert uttryck
Exempel på automatisering av test (med assert)¶
In [24]:
def keep_even_values(values):
output = []
for value in values:
if value % 2 == 0:
output.append(value)
return output
def test_keep_even_values():
try:
assert keep_even_values([]) == []
assert keep_even_values([1]) == []
assert keep_even_values([2]) == [2]
assert keep_even_values([1, 2, 3, 4]) == [2, 4]
return True
except AssertionError:
return False
if test_keep_even_values():
print("keep_even_values() fungerar.")
else:
print("keep_even_values() fungerar INTE.")
keep_even_values() fungerar.
Skriva till fil¶
Öppna fil för att läsa/skriva¶
open(filepath, mode)
returnerar den öppnade filen som ettfile
-objekt.mode
är en sträng vars defaultvärde är'r'
som i read- Andra giltiga värden på mode är
'w'
som i write och'a'
som i appendmode='r'
read: vi kan endast läsa från filenmode='w'
write: vi kan endast skriva till filen, existerande innehåll skrivs övermode='a'
append: vi kan endast skriva till filen, existerande innehåll bevaras, nytt innehåll läggs till slutet på filen
- Metoden
file.write(s)
skriver strängens
till filenfile
.- Om inte filen redan finns så skapas den när vi skriver till den.
Öppna fil med explicit teckenkodning¶
open(filepath, mode, encoding='utf-8')
- Vi kan tvinga python att öppna filen med en viss teckenkodning genom att använda nyckelordsargumentet encoding. Kan behövas t.ex. om man kör Windows och ska läsa/skriva textfiler kodade i utf-8.
Exempel, skriva till fil¶
In [9]:
# öppna fil för att lägga till text till
datafile = open("filnamn.txt", "a")
datafile.write("Glöm inte bort")
datafile.write("radbryt!\n")
# stäng filen som vi öppnat
datafile.close()
Repetition¶
Exempel på aggregering av data¶
- City kan tillhandahålla befolkningssiffra genom att fråga alla hushåll efter hur många som ingår i hushållet.
Association och scope¶
- Associationspil implementeras som en variabel i "från"-klassen.
- Om fler än 1 referens ska finnas, använd t.ex. lista eller dictionary
- Samma variabelnamn och metodnamn kan användas i flera klasser.
Vad händer när vi skapar en instans av en klass?¶
- Skapa instans av klass:
Klassnamn(arg1, arg2, ..., argn)
- Python skapar ett objekt och kör klassens
__init__()
-metod - Objektet som skapats skickas som det första argumentet till
__init__()
. - Argument som skickats med vid anrop av klassnamnet skickas vidare till
__init__()
efterself
.
Hur kan nedanstående tolkas - syntax är viktigt¶
a
a()
a(b)
a[b]
a.b
a.b()
a.b = c
a = b.c
a = b.c()
Beståndsdelar i ett program¶
- värden (values): t.ex. siffror eller text (kallas för strängar)
- operatorer (operators): token/symbol som står för en instruktion
- variabler (variables): namngivna referenser till värden
- uttryck (expressions): ett uttryck kan beräknas till ett värde (ibland säger man även utvärderas istället för beräknas)
- nyckelord (keywords): speciella ord som refererar till instruktioner som ska utföras
- skiljetecken (punctuators)
- satser (statements): fullständig instruktion