Enhetstesting i Python: Master unittest-modulen nå!

Grunnleggende om enhetstesting med Python

Dyktige utviklere publiserer aldri kode uten å ha testet den grundig. Enhetstesting er en prosess der man tester de separate delene av et større program.

Denne artikkelen gir deg en innføring i hvordan du kan utføre enhetstesting av din kode ved hjelp av Python sin innebygde `unittest`-modul. La oss først se nærmere på ulike typer testing.

Når det gjelder testing, skiller vi mellom manuell og automatisert testing. Manuell testing innebærer at mennesker fysisk utfører testene etter at utviklingen er fullført. Automatisert testing, derimot, bruker programmer til å utføre testene og presentere resultatene automatisk.

Det er åpenbart at manuell testing kan være tidkrevende og vanskelig å gjennomføre. Derfor skriver utviklere kode for å automatisere testprosessen. Innenfor automatisert testing finner vi forskjellige typer, som for eksempel enhetstesting, integrasjonstesting, ende-til-ende testing og stresstesting, blant andre.

La oss se på den vanlige prosessen for testing:

  • Skriv eller oppdater koden.
  • Skriv eller oppdater tester for forskjellige scenarier for koden din.
  • Kjør testene (manuelt eller med en testkjører).
  • Analyser testresultatene. Hvis det finnes feil, korriger dem og gjenta prosessen.

Her skal vi fokusere på den viktigste og mest grunnleggende typen testing: enhetstesting. La oss dykke rett inn i detaljene.

Hva er Enhetstesting?

Enhetstesting er en metode for å teste små, uavhengige kodeblokker. Ofte vil en slik blokk være en enkelt funksjon. At koden er uavhengig betyr at den ikke er avhengig av andre deler av prosjektet.

Tenk deg at vi skal verifisere om en streng er identisk med «tipsbilk.net». Vi lager da en funksjon som tar inn et argument og returnerer `True` eller `False` avhengig av om strengen matcher eller ikke.

def sjekk_likhet(streng):
    return streng == "tipsbilk.net"

Funksjonen over er isolert og avhenger ikke av annen kode. Vi kan derfor teste den separat med ulike inndata. Slike uavhengige kodeblokker kan gjenbrukes i hele prosjektet.

Betydningen av Enhetstesting

Siden uavhengige kodeblokker ofte er gjenbrukbare, er det avgjørende at de er godt skrevet og testet. Enhetstester er verktøyet vi bruker for å kvalitetssikre disse kodeblokkene. Hva kan skje dersom vi ikke bruker enhetstester?

Dersom vi ikke tester disse små kodebitene, kan det medføre at andre tester, som integrasjonstester og ende-til-ende tester, mislykkes. Dette kan føre til at hele applikasjonen bryter sammen. Det er derfor kritisk at de fundamentale byggesteinene i koden er godt testet.

Nå forstår vi hvorfor enhetstesting er så viktig. Når vi har gjort grundig enhetstesting, er det mindre sannsynlig at andre tester vil feile på grunn av feil i disse uavhengige kodeblokkene.

I de kommende avsnittene skal vi utforske hva Python sin `unittest`-modul er, og hvordan vi kan bruke den til å skrive enhetstester i Python.

Merk: Vi forutsetter at du er kjent med Python-konsepter som klasser og moduler. Hvis du ikke er komfortabel med disse konseptene, kan det være vanskeligere å forstå de kommende delene.

Hva er Python `unittest`?

Python sin `unittest`-modul er et innebygd rammeverk for testing av Python-kode. Den inkluderer en testkjører som gjør det enkelt å utføre tester. Vi kan altså bruke denne modulen direkte, uten å være avhengig av eksterne moduler (selv om dette kan endre seg basert på prosjektets behov). Den innebygde `unittest`-modulen er et flott sted å starte med testing i Python.

For å teste Python-kode med `unittest`, må vi følge disse stegene:

#1. Skriv koden.

#2. Importer `unittest`-modulen.

#3. Lag en fil som starter med ordet `test`. For eksempel `test_primtall.py`. Ordet `test` identifiserer filen som en testfil.

#4. Lag en klasse som arver fra klassen `unittest.TestCase`.

#5. Skriv metoder (tester) inne i klassen. Hver metode representerer en testcase. Testmetodenavn skal begynne med ordet `test`.

#6. Kjør testene. Det finnes ulike metoder for å kjøre testene:

  • Bruk kommandoen `python -m unittest test_filnavn.py`.
  • Kjør testfilene som vanlige Python-filer med `python test_filnavn.py`. For at dette skal fungere, må vi kalle på `unittest` sin `main`-metode i testfilen.
  • Til slutt kan vi bruke `discover`-metoden for automatisk å finne og kjøre tester med `python -m unittest discover`, uten å måtte spesifisere filnavnene. Dette forutsetter at testfilene følger navnekonvensjonen vi har beskrevet.

I testing sammenligner vi vanligvis resultatet av koden med et forventet resultat. `unittest` har ulike metoder for å utføre disse sammenligningene. En oversikt over disse metodene finner du her.

Disse metodene er enkle å forstå og bruke.

Det er nok teori. La oss se på litt kode.

Merk: Hvis du har spørsmål om `unittest`-modulen, se dokumentasjonen. La oss nå bruke `unittest`-modulen i praksis.

Enhetstester med Python og `unittest`

Først skal vi skrive noen funksjoner. Deretter skal vi fokusere på å skrive testene. Åpne en mappe i din foretrukne kodeeditor og lag en fil med navnet `utils.py`. Lim inn følgende kode i filen:

import math

def er_primtall(n):
    if n < 0:
        return 'Negative tall er ikke tillatt'
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

def kubikk(a):
    return a * a * a

def hilsen(navn):
    return "Hei, " + navn

Vi har tre forskjellige funksjoner i `utils.py`. Nå skal vi teste hver av disse funksjonene med ulike testcases. La oss starte med testene for den første funksjonen `er_primtall`.

#1. Lag en fil som heter `test_utils.py` i samme mappe som `utils.py`.

#2. Importer `utils` og `unittest`-modulen.

#3. Lag en klasse med navnet `TestUtils` som arver fra `unittest.TestCase`. Klassens navn kan være hva som helst, men det bør være beskrivende.

#4. I klassen skriver du en metode som heter `test_er_primtall` som aksepterer `self` som argument.

#5. Skriv forskjellige testcases som tester `er_primtall` med ulike inndata, og sammenlign med forventet resultat.

#6. Et eksempel på en testcase er `self.assertFalse(utils.er_primtall(1))`, som tester om 1 er et primtall (som den ikke er).

#7. I tilfellet over forventer vi at `er_primtall(1)` skal returnere `False`.

#8. Tilsvarende skriver vi ulike testcases basert på funksjonen som testes.

La oss se på testene:

import unittest
import utils

class TestUtils(unittest.TestCase):
    def test_er_primtall(self):
        self.assertFalse(utils.er_primtall(4))
        self.assertTrue(utils.er_primtall(2))
        self.assertTrue(utils.er_primtall(3))
        self.assertFalse(utils.er_primtall(8))
        self.assertFalse(utils.er_primtall(10))
        self.assertTrue(utils.er_primtall(7))
        self.assertEqual(utils.er_primtall(-3),
                         "Negative tall er ikke tillatt")

if __name__ == '__main__':
    unittest.main()

Vi kaller på `unittest`-modulens `main`-metode for å kjøre testene med kommandoen `python filnavn.py`. Kjør testene nå.

Du vil se en output som ligner på dette:

$ python test_utils.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Prøv nå å skrive tester for de andre funksjonene også. Tenk på ulike scenarier for funksjonene og skriv tester for dem. Her er et eksempel på tester som er lagt til i klassen over:

...

class TestUtils(unittest.TestCase):
    def test_er_primtall(self):
        ...
    def test_kubikk(self):
        self.assertEqual(utils.kubikk(2), 8)
        self.assertEqual(utils.kubikk(-2), -8)
        self.assertNotEqual(utils.kubikk(2), 4)
        self.assertNotEqual(utils.kubikk(-3), 27)

    def test_hilsen(self):
        self.assertEqual(utils.hilsen("tipsbilk.net"), "Hei, tipsbilk.net")
        self.assertEqual(utils.hilsen("Chandan"), "Hei, Chandan")
        self.assertNotEqual(utils.hilsen("Chandan"), "Hi, Chandan")
        self.assertNotEqual(utils.hilsen("Hafeez"), "Hi, Hafeez")
...

Vi brukte noen få sammenligningsfunksjoner fra `unittest`-modulen. Du finner hele listen her.

Vi har nå lært hvordan man skriver enhetstester med `unittest`-modulen. La oss se på ulike metoder for å kjøre testene.

Hvordan kjøre tester med `unittest`

Vi har allerede sett en metode for å kjøre tester. La oss se på de to andre metodene.

#1. Bruke filnavn og `unittest`-modulen:

Her bruker vi `unittest`-modulen sammen med filnavnet for å kjøre testene. Kommandoen er `python -m unittest filnavn.py`. I vårt tilfelle er det `python -m unittest test_utils.py`.

#2. Bruke `discover`-metoden:

Vi bruker `discover`-metoden fra `unittest`-modulen for å automatisk finne og kjøre alle testfilene. For at dette skal fungere, må filnavnene starte med `test`.

Kommandoen for å kjøre testene med `discover` er `python -m unittest discover`. Denne kommandoen finner alle filer som starter med `test` og kjører dem.

Konklusjon 👩‍💻

Enhetstesting er en fundamentalt viktig del av programvareutvikling. Det finnes mange andre typer tester i den virkelige verden, og du bør prøve å lære dem gradvis. Jeg håper denne artikkelen hjelper deg med å komme i gang med å skrive grunnleggende tester i Python ved hjelp av `unittest`-modulen. Det finnes også tredjepartsbiblioteker som pytest, Robot Framework, nose, nose2, slash, etc. som du kan utforske basert på dine behov.

Lykke til med testingen 😎

Du kan også være interessert i Python-intervjuspørsmål og -svar.