
12 minute read
2.6 Enhetstester
oppstått i utgangspunktet. Det er imidlertid umulig å ikke gjøre feil i ny og ne, men en god strategi kan være som følger: Fiks, le og lær! Først retter du opp det som gikk galt, så ler du litt, og så gjør du det du kan for å unngå at det skjer igjen. I vårt tilfelle lærte vi at vi alltid må dobbeltsjekke bindinger i koden. Bonusen ble at vi fikk et eksempel vi kunne bruke til å demonstrere feilsøking og testing.
Kanskje skjønte du hva feilen var, allerede før vi begynte å feilsøke. Det er i så fall kjempebra! Det er ofte lettere å se feil i koden som andre har skrevet, enn i koden man skriver selv. Dette er en av grunnene til at programmerere ofte ser over hverandres kode, gjerne mens de skriver den.
Slik ser src/routes ut når vi jobber med dette delkapittelet.
En samling med funksjoner som kan være til nytte flere steder i et dataprogram, kalles for et bibliotek (library på engelsk).
En enhetstest er en test som sjekker et lite stykke kode, altså en enhet. Det vanligste formålet med en enhetstest er å sjekke at dataene vi får tilbake fra en funksjon, stemmer overens med det vi forventer. I motsetning til testene vi lærte om i delkapittel 2.4, utfører vi ikke disse enhetstestene selv. I stedet skriver vi kode og lar datamaskinen utføre testene for oss.
I dette delkapittelet jobber vi med disse filene:
• • • _bibliotek/hjelpemidler-strenger.js er et bibliotek vi holder på å skrive. _komponenter/ErLikTest.svelte er en komponent som utfører én enhetstest. streng-testsamling.svelte er en testsamling med hjelpemiddelstrenger som bruker komponenten ErLikTest til å utføre tester.
Vi begynner med å lage ErLikTest.svelte som skal regne ut utfallet av en test og si om resultatet er som forventet eller ikke.
1 <!-- _komponenter/ErLikTest.svelte --> 2 <script> 3 /** Funksjonen vi ønsker å teste. */ 4 export let funksjon 5 /** Argumentene funksjonen skal få. Oppgis som et array. */ 6 export let inndata 7 /** Resultatet vi forventer at funksjonen skal returnere. */ 8 export let forventetUtdata
9 10 let feil 11 let erLike 12 let resultat
13 14 try {
15 /* 16 * Når vi skriver "enFunksjon(...etArray)",
17 18 * legger datamaskinen inn verdiene i etArray * som argumenter til funksjonen:
19 20 21 * Første verdi blir første argument, andre verdi * blir andre argument og så videre. */
22 resultat = funksjon(...inndata) 23 erLike = resultat === forventetUtdata 24 } catch (error) {
25 /* 26 * Fordi funksjon er under utvikling mens vi tester,
27 * kan den krasje. I så fall lagrer vi feilen
28 * til variabelen "feil" og logger den til konsollen.
29 */ 30 feil = error 31 console.log(error)
32 } 33 </script>
34 35 <!-36 Emojier som gjør det lett å se om testene 37 passerte, feilet eller krasjet. 38 Vi har valgt emojier med forskjellig form 39 og farge for å hjelpe fargeblinde. 40 -->
41 {#if feil}��{:else if erLike}✅ {:else}❌{/if}
42 <!-43 Viser forventede inndata og utdata. 44 Bruker JSON.stringify for å vise kodedetaljer, 45 som hakeparenteser og hermetegn, rundt dataene. 46 --> 47 <span class="data">{JSON.stringify(inndata)}</span>
48 -> 49 <span class="data">{JSON.stringify(forventetUtdata)}</span> 50 <!-- Hvis testen feilet eller krasjet, vil vi vise mer. --> 51 {#if feil || !erLike} 52 <ul> 53 <li>
54
55 {#if feil} <!--
56
57 58 --> Begrenser oss til feil.message, som er en oppsummering. Brukeren kan åpne konsollen for å se hele feilmeldingen.
59 KRASJET: <span class="data">"{feil.message}"</span>
60 {:else}
61
Fikk -> <span class="data">{JSON.stringify(resultat)}</span> 62 {/if} 63 </li> 64 </ul> 65 {/if}
66 67 <style> 68 .data { 69 font-family: monospace; /* Kodeskrift */ 70 white-space: pre; /* La alle mellomrom stå. */
71 } 72 </style>
Øverst i komponenten har vi angitt følgende egenskaper: funksjon, inndata og forventetUtdata. Ovenfor disse har vi skrevet kommentarer som forklarer hva slags data de skal motta. Like nedenfor har vi angitt de tre variablene feil, erLike og resultat, som vi skal bruke til å lagre utfallet av testen.
I de siste linjene i script-blokken kaller vi på funksjonen. Vi har lagt dette funksjonskallet inni en try-catch-blokk. En try-catch-blokk prøver å gjøre det som står mellom krøllparentesene etter try. Hvis det oppstår en feil som ville fått programmet til å krasje, angrer datamaskinen på alt den har gjort hittil, og gjør i stedet det som står mellom krøllparentesene etter catch. Du får se nytten av dette ganske snart.
I HTML-delen av filen viser vi informasjon om hvordan testen gikk. Aller først viser vi en emoji som varierer etter om testen passerte, feilet eller krasjet. Etter emojien viser vi forventede inndata og utdata. Hvis funksjonen krasjet eller testen feilet, viser vi hva som var galt, som et kulepunkt under testen.
Try-catch-blokker
En try-catch-blokk ser slik ut:
1 try { 2 /* 3 * Datamaskinen prøver å gjøre det 4 * som står mellom krøllparentesene, 5 * men hvis en feil som ville fått 6 * programmet til å krasje, oppstår, … 7 */ 8 } catch (error) { 9 /* 10 * … angrer datamaskinen på alt den gjorde i try, 11 * og gjør det som står mellom 12 * krøllparentesene etter catch. 13 * Feilen som oppsto, er lagret i parameteren error.
14 */ 15 }
Du kan tenke på en try-catch-blokk som en variant av en if-else-setning: if
(alt kjører uten å krasje) { /* gjør det vanlige */ }
else { /* gjør noe annet */ }. Forskjellen på en try-catch-blokk og en if-else-setning er at datamaskinen ikke vet hvilken vei den skal velge i trycatch-blokken, før den starter, og derfor må prøve seg frem.
UNNGÅ Å BRUKE TRY-CATCH MED MINDRE DU ABSOLUTT MÅ
Try-catch-blokker er en nødløsning. Vi tyr til dem når det viser seg å være umulig å skrive en if-else-setning som forhindrer feilen.
I ErLikTest.svelte tar vi inn en hvilken som helst funksjon. Siden det finnes uendelig mange måter en ukjent funksjon kan krasje på, kan vi umulig skrive en if-else-setning som beskytter oss mot alle disse krasjene. Derfor tillater vi oss å bruke try-catch i ErLikTest.svelte.
I oppgavedelen skal du legge inn flere funksjoner i hjelpemidler-strenger.js og lage tester for dem.
LAGE FUNKSJONENE VI SKAL TESTE
Før vi begynner med å lage tester, må vi ha et utkast til det vi skal teste. Vi åpner filen hjelpemidler-strenger.js og lager funksjonen gjoerForbokstavStor. Som tidligere nevnt, er det som regel en fordel å skrive testene før man skriver koden. Derfor setter vi inn en funksjon som bare inneholder return. Da gjør ikke funksjonen noe.
1 // _bibliotek/hjelpemidler-strenger.js 2 export let gjoerForbokstavStor = (streng) => { 3 return
4 }
BEGYNNE PÅ TESTSAMLINGEN
Vi begynner på testsamlingen ved å lage én enkelt test for funksjonen gjoerForbokstavStor. Vi åpner filen streng-testsamling.svelte. Vi importerer funksjonen gjoerForbokstavStor og komponenten ErLikTest til denne filen og skriver testen.
1 <!-- streng-testsamling.svelte --> 2 <script> 3 import { gjoerForbokstavStor } from "./_bibliotek/hjelpemidler-strenger.js" 4 import ErLikTest from "./_komponenter/ErLikTest.svelte" 5 </script>
6 7 <h1>Enhetstester for hjelpemidler-strenger.js</h1>
8 9 <h2>gjoerForbokstavStor</h2> 10 <!-- Legg merke til arrayet i inndataene. --> 11 <ErLikTest 12 inndata={["liten"]} 13 forventetUtdata={"Liten"} 14 funksjon={gjoerForbokstavStor}
15 />
Når vi åpner nettsiden /streng-testsamling, viser den testen som feilet. Dette er et godt tegn, for nå vet vi at ErLikTest-komponenten faktisk kommer til å si fra hvis dataene ikke er som forventet.
LAGE EN DEKKENDE TESTSAMLING FOR gjoerForbokstavStor
Som nevnt i delkapittel 2.4 bør en testsamling være dekkende. Det vil si at den kontrollerer det meste som brukerne kan finne på å gjøre når de bruker programmet.
Vi ser for oss at funksjonene vi lager i hjelpemidler-strenger.js, først og fremst skal brukes av norskspråklige programmerere. Vi kan blant annet sette sammen funksjonene slik at de gjør om en streng som en bruker har skrevet inn, til et egnet variabelnavn, for eksempel «hent2SoekeresultaterFraNorge». Derfor kan vi begrense oss til å teste tilfeller som består av de 29 bokstavene i det norske alfabetet, tall og mellomrom.
Nedenfor ser du testtilfellene vi har laget. Vi har lagt testtilfellene i arrayet storForbokstavTilfeller, slik at vi kan iterere over dem. I linjen før hver verdi i arrayet har vi skrevet en kommentar om hvorfor vi har med akkurat den verdien.
Nederst har vi laget en each-løkke som lager en ErLikTest-komponent for hvert testtilfelle.
1 <!-- streng-testsamling.svelte --> 2 <script> 3 import { gjoerForbokstavStor } from "./_bibliotek/hjelpemidler-strenger.js" 4 import ErLikTest from "./_komponenter/ErLikTest.svelte"
5 6 // Første verdi er inndataene, andre er resultatet. 7 let storForbokstavTilfeller = [ 8 // Tom streng, fordi man ofte glemmer denne 9 [[""], ""], 10 // Grunntilfelle: én bokstav 11 [["i"], "I"], 12 // Flere bokstaver, men ikke norske 13 [["liten"], "Liten"], 14 // Starter med norsk bokstav 15 [["år"], "År"], 16 // Starter allerede med stor bokstav 17 [["Stor"], "Stor"], 18 // Starter med tall 19 [["1-er"], "1-er"], 20 // Hvis strengen starter med mellomrom, skal ingenting skje. 21 [[" fortsatt liten"], " fortsatt liten"], 22 // Streng som inneholder komma 23 [["ja, vi elsker"], "Ja, vi elsker"], 24 // Ikke bruk store bokstaver etter punktum. 25 [["bare. første"], "Bare. første"],
26 ] 27 </script>
28 29 <h1>Enhetstester for hjelpemidler-strenger.js</h1> 30 <h2>gjoerForbokstavStor</h2> 31 <ul> 32 {#each storForbokstavTilfeller as data} 33 <li>
34 <ErLikTest
35 36 37 38 /> 39 </li> 40 {/each} 41 </ul> inndata={data[0]} forventetUtdata={data[1]} funksjon={gjoerForbokstavStor}

Vi har valgt disse testtilfellene, slik at vi har testtilfeller som starter både med internasjonale bokstaver, særnorske bokstaver, mellomrom og tall. Vi ser for oss at dette representerer de strengene brukerne våre kan tenkes å sende inn til funksjonen. Vi valgte også å ha med punktum, bindestrek og kommaer i noen av strengene (men ikke som første tegn) fordi dette er tegn som ofte forekommer i en norsk tekststreng.
Vi har også tatt med et testtilfelle med en tom streng. Mange funksjoner som fungerer på strenger med ett eller flere tegn, kan krasje hvis strengen er tom. Vi glemmer ofte å tenke på tomme strenger når vi skriver kode, enda det er vanlig når brukeren ikke har skrevet noe i et input-felt.
Tomme strenger og arrayer
Det er vanlig at funksjoner må håndtere tomme strenger, for eksempel når et input-felt er tomt. Husk å ta med testdata med tomme strenger for funksjoner som behandler strenger, med mindre du vet at funksjonen aldri kommer til å motta tomme strenger. Hvis du skriver tester der inndataene er et array, bør du også lage testtilfeller der arrayet er tomt: [].
FÅ FUNKSJONEN TIL Å GJØRE SOM TESTENE VIL
Når vi har skrevet testsamlingen, går det neste steget ut på å få funksjonen til å gjøre det testene ber om. Derfor skal vi fylle ut gjoerForbokstavStor slik at testene lykkes. Gjør gjerne et forsøk selv før du leser løsningsforslaget vårt. Ha testsamlingen åpen i nettleseren og se hva som skjer underveis.
Vi begynner med å returnere strengen akkurat som vi fikk den, og allerede da blir nesten halvparten av tilfellene grønne, det vil si de tilfellene der teksten skal være uendret, men det er i alle fall en start.
EKSTRASTOFF
Testtilfeller vi har utelatt
En gang i fremtiden kan det hende vi skal bruke gjoerForbokstavStor på en måte som gir oss større variasjon i inndataene. Vi kunne for eksempel ha brukt funksjonen til å gi brukerne av en chattetjeneste muligheten til å rette meldingene sine automatisk. I så fall burde vi lage testtilfeller der strengene starter med emojier og annet som chattemeldinger typisk starter med. Men siden funksjonen bare skal brukes av programmerere foreløpig, får vi lite igjen for å skrive slike testtilfeller nå. Programmererne skriver sannsynligvis ikke inn slike tegn. Vi kan heller legge til slike testtilfeller etter hvert. For å si det billedlig: En fabrikk som lager bokseåpnere, tester bare bokseåpnerne på hermetikkbokser, ikke på tankskip og pengeskap.
1 // _bibliotek/hjelpemidler-strenger.js 2 export let gjoerForbokstavStor = (streng) => { 3 return streng
4 }

Nå gjør vi et forsøk på å gjøre første bokstav stor. Etter litt prøving og feiling har vi følgende kode:
// hjelpemidler-strenger.js export let gjoerForbokstavStor = (streng) => { let foersteBokstav = streng[0] let resten = streng.slice(1) return foersteBokstav.toUpperCase() + resten

Dette forsøket var stort sett vellykket, men funksjonen krasjer på den første testen i samlingen. For å se hele feilen åpner vi konsollen. Nedenfor ser du hvordan det ser ut i Chrome.

Opplistingen ovenfor viser hvor datamaskinen befant seg i koden da krasjet som vi avverget, oppsto. Når vi skal løse en slik feil, starter vi øverst i listen og jobber oss nedover for å se om vi finner årsaken.
I starten av hver linje står navnet på funksjonen eller komponenten datamaskinen var inni da krasjet oppsto. I slutten av hver linje er det en lenke med formatet filnavn:linjenummer:tegnnummer. Denne angir filen og nummeret på linjen og tegnet der datamaskinen befant seg. Vi kan klikke på lenken for å se filen i utviklerverktøyet, eller vi kan finne filen i utviklerverktøyet. Nedenfor ser du hvor vi havner når vi klikker på det første resultatet.
Hvis inndataene er "", altså en helt tom streng, vet vi at det skaper trøbbel fordi variabelen foersteBokstav har verdien undefined. Som feilmeldingen sa, kan vi ikke skrive .toUpperCase() i en variabel som inneholder undefined. For å unngå krasjen kan vi sjekke om strengen har lengde 0 og i så fall returnere en tom streng og dermed avslutte kjøringen av koden i funksjonen.

1 // hjelpemidler-strenger.js 2 export let gjoerForbokstavStor = (streng) => { 3 if (streng.length === 0) { 4 return ""
5 } 6 let foersteBokstav = streng[0] 7 let resten = streng.slice(1) 8 return foersteBokstav.toUpperCase() + resten
9 }
Og da går alt bra!

REFAKTORERE – GJØRE KODEN MER ELEGANT
Når funksjonen har passert alle testene våre, lagrer vi funksjonen. Deretter prøver vi, det vil si vi prøver å finne ut om det er mulig å gjøre funksjonen kortere og mer elegant . Det kalles å refaktorere.
I vårt tilfelle har vi jo måttet legge til en if-setning for å ta høyde for tomme strenger. Finnes det en måte å hente ut første bokstav på der vi ikke ender opp med undefined, slik at vi kan kutte ut if-setningen?
Hvis du ikke kjenner til alle muligheter, kan det være nyttig å lese dokumentasjonen for datatypen du jobber med. I dette tilfellet kan du søke på «w3schools string documentation» på nettet. Da vil «JavaScript String Reference - W3Schools» være et av de øverste resultatene:
https://www.w3schools.com/jsref/jsref_obj_string.asp

Hvis du klikker på lenken til dokumentasjonen for charAt, får du vite litt om hvordan funksjonen fungerer. Under overskriften «Return value» står det at resultatet blir en tom treng hvis indeksen er ugyldig, altså hvis det ikke finnes noen bokstav på den nevnte plassen. Da blir "".charAt(0) === "" i stedet for undefined. Dette kan vi teste i konsollen.
