Tilbake
8.2
Testing og kvalitetssikring

8.2 Testing og kvalitetssikring

Enhetstesting, integrasjonstesting og testdrevet utvikling.

60 min
6 oppgaver
EnhetstestingTDDIntegrasjonstestKvalitet
Du leser den tradisjonelle versjonen
Din fremgang i kapitlet
0 / 6 oppgaver

Testing er en kritisk del av programvareutvikling. Godt testede programmer har færre feil, er enklere å vedlikeholde og gir utviklere trygghet når de gjør endringer. I dette kapittelet skal vi se på ulike typer testing – fra enhetstester av individuelle funksjoner til integrasjonstester av hele systemet.

Vi skal også se på Test-Driven Development (TDD) og betydningen av code review for kvalitetssikring.

Testpiramiden

Testpiramiden viser hvordan ulike typer tester bør balanseres:

       /\
      /  \     E2E tests (få)
     /____\
    /      \   Integrasjonstester (middels)
   /________\
  /          \ Enhetstester (mange)
 /____________\

Enhetstester (Unit tests)

Tester én enkelt funksjon eller metode isolert:
- Rask kjøretid (millisekunder)
- Enkle å skrive og vedlikeholde
- Finner bugs tidlig
- Fungerer som dokumentasjon av forventet oppførsel

Biblioteker: unittest (Python standard), pytest

Integrasjonstester

Tester samspillet mellom flere komponenter:
- Database + backend
- API + frontend
- Flere moduler sammen

End-to-End (E2E) tester

Tester hele systemet fra brukerens perspektiv:
- Simulerer ekte brukerscenarier
- Treg kjøretid (sekunder/minutter)
- Mer kompleks å skrive og vedlikeholde
- Gir høyest tillit til at systemet fungerer

Verktøy: Selenium, Playwright, Cypress

Test-Driven Development (TDD)

TDD er en utviklingsmetode der tester skrives før koden:

1. Red – Skriv en test som feiler (funksjonen finnes ikke ennå)
2. Green – Skriv minimal kode for å få testen til å passere
3. Refactor – Forbedre koden uten å endre oppførsel

Fordeler:
- Tvinger deg til å tenke på grensesituasjoner
- All kode er testet by design
- Enklere å refaktorere senere

Ulemper:
- Tar lenger tid i starten
- Krever disiplin

Code Review

Code review er prosessen der andre utviklere leser og kommenterer koden din før den merges:

Hva ser vi etter:
- Logiske feil
- Manglende feilhåndtering
- Usikker kode (SQL injection, XSS)
- Dårlig navngiving eller struktur
- Manglende tester

Best practices:
- Hold pull requests små (< 400 linjer)
- Vær konstruktiv, ikke personangrep
- Forklar hvorfor, ikke bare hva
- Anerkjenn god kode

Testdekningsgrad (Code Coverage)

Måler hvor stor prosentandel av koden som kjøres av testene:
- 80-90% ansees som godt
- 100% er sjelden nødvendig eller praktisk
- Høy coverage garanterer ikke kvalitet, men lav coverage er et dårlig tegn

✏️Eksempel: Enhetstesting med unittest
Funksjon å teste:

# calculator.py

def add(a, b):
    """Legger sammen to tall."""
    return a + b

def divide(a, b):
    """Deler a på b."""
    if b == 0:
        raise ValueError("Kan ikke dele på null")
    return a / b

def is_even(n):
    """Sjekker om et tall er partall."""
    return n % 2 == 0

Tester med unittest:

# test_calculator.py
import unittest
from calculator import add, divide, is_even

class TestCalculator(unittest.TestCase):

    def test_add_positive_numbers(self):
        self.assertEqual(add(2, 3), 5)

    def test_add_negative_numbers(self):
        self.assertEqual(add(-2, -3), -5)

    def test_divide_normal(self):
        self.assertEqual(divide(10, 2), 5)

    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            divide(10, 0)

    def test_is_even_true(self):
        self.assertTrue(is_even(4))

    def test_is_even_false(self):
        self.assertFalse(is_even(5))

    def test_is_even_zero(self):
        self.assertTrue(is_even(0))

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

Kjøre testene:

python test_calculator.py -v

Output:

test_add_negative_numbers (__main__.TestCalculator) ... ok
test_add_positive_numbers (__main__.TestCalculator) ... ok
test_divide_by_zero (__main__.TestCalculator) ... ok
test_divide_normal (__main__.TestCalculator) ... ok
test_is_even_false (__main__.TestCalculator) ... ok
test_is_even_true (__main__.TestCalculator) ... ok
test_is_even_zero (__main__.TestCalculator) ... ok

----------------------------------------------------------------------
Ran 7 tests in 0.001s

OK

✏️Eksempel: Enhetstesting med pytest

Pytest er et populært alternativ med enklere syntaks:

# test_calculator_pytest.py
import pytest
from calculator import add, divide, is_even

def test_add_positive():
    assert add(2, 3) == 5

def test_add_negative():
    assert add(-2, -3) == -5

def test_divide_normal():
    assert divide(10, 2) == 5

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Kan ikke dele på null"):
        divide(10, 0)

def test_is_even():
    assert is_even(4) == True
    assert is_even(5) == False
    assert is_even(0) == True

# Parametriserte tester - kjører samme test med forskjellige input
@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_add_parametrized(a, b, expected):
    assert add(a, b) == expected

Kjøre pytest:

pytest test_calculator_pytest.py -v

Fordeler med pytest:
- Enklere syntaks (bare assert, ikke self.assertEqual)
- Bedre feilmeldinger
- Parametriserte tester
- Fixtures for oppsett/nedriving

✏️Eksempel: TDD i praksis
Oppgave: Lag en funksjon validate_email(email) som returnerer True hvis e-post er gyldig.

Steg 1 (Red) – Skriv test som feiler:

# test_email.py
from email_validator import validate_email

def test_valid_email():
    assert validate_email("bruker@example.com") == True

def test_missing_at_sign():
    assert validate_email("brukerexample.com") == False

def test_missing_domain():
    assert validate_email("bruker@") == False

Kjører testene → FEIL (funksjonen finnes ikke)

Steg 2 (Green) – Skriv minimal kode:

# email_validator.py
def validate_email(email):
    if '@' not in email:
        return False
    if email.endswith('@'):
        return False
    return True

Kjører testene → OK

Steg 3 (Refactor) – Forbedre koden:

# email_validator.py
def validate_email(email):
    """Validerer e-postadresse."""
    if '@' not in email:
        return False

    parts = email.split('@')
    if len(parts) != 2:
        return False

    local, domain = parts
    if not local or not domain:
        return False

    if '.' not in domain:
        return False

    return True

Steg 4 – Legg til flere tester:

def test_no_dot_in_domain():
    assert validate_email("bruker@example") == False

def test_multiple_at_signs():
    assert validate_email("bru@ker@example.com") == False

Kjører testene → Oppdager at multiple_at_signs feiler → Forbedre koden → Gjenta

Oppgave 1 (Flervalg)
Hva er hovedformålet med enhetstester?

A) Teste hele systemet fra brukerens perspektiv
B) Teste én enkelt funksjon isolert
C) Teste samspillet mellom database og backend
D) Teste sikkerhet og ytelse

Oppgave 2 (Flervalg)
I hvilken rekkefølge utføres stegene i Test-Driven Development (TDD)?

A) Green → Red → Refactor
B) Refactor → Red → Green
C) Red → Green → Refactor
D) Red → Refactor → Green

Oppgave 3
Forklar kort forskjellen mellom enhetstester og integrasjonstester.

Oppgave 4
Skriv en unittest for denne funksjonen:

def is_palindrome(text):
    """Sjekker om en tekst er et palindrom."""
    cleaned = text.lower().replace(" ", "")
    return cleaned == cleaned[::-1]

Test med minst tre forskjellige input (inkludert grensesituasjoner).

Oppgave 5
En funksjon get_grade(score) skal returnere karakter basert på poengsum:
- 90-100: "A"
- 80-89: "B"
- 60-79: "C"
- 0-59: "F"

Skriv pytest-tester for denne funksjonen (inkludert grenseverdier).

Oppgave 6
Hva er code coverage, og hvorfor er 100% coverage ikke alltid et meningsfullt mål?

Oppgave 7
Nevn tre ting du bør se etter når du gjør code review av en kollegas pull request.

Oppgave 8
Forklar kort hva "Red-Green-Refactor"-syklusen i TDD betyr.

// --- Samleoppgaver ---

Samleoppgave 1
Du har laget en BankAccount-klasse med metodene deposit(), withdraw() og get_balance(). Withdraw skal kaste en InsufficientFundsError hvis saldo er for lav.

a) Skriv komplette unittest-tester for alle tre metoder.
b) Inkluder minst én test som verifiserer at riktig exception kastes.
c) Legg til en test for grensesituasjonen der withdraw prøver å ta ut nøyaktig saldoen.

Samleoppgave 2
Et team har 70% testdekningsgrad på en Flask-webapplikasjon. Teamlederen krever 95% coverage før lansering.

a) Forklar hvorfor høy testdekningsgrad ikke garanterer feilfri kode.
b) Hvilke deler av en webapplikasjon er mest kritiske å teste grundig?
c) Hva er forskjellen på enhetstester og end-to-end-tester for en slik app?
d) Hvordan kan teamet bruke TDD for ny funksjonalitet fremover?

Oppsummering

I dette kapittelet har du lært:

- Testing: sikrer kvalitet i programvaren.
- Enhetstesting: tester enkeltdeler med unittest eller pytest.
- TDD: skrive test før kode.
- Kvalitetssikring: systematisk feilfinning.
- Automatisering: tester kjøres automatisk.

Noekkelbegreper


BegrepForklaring
EnhetstestTest av en enkelt kodedel
TDDTestdrevet utvikling (test før kode)
pytestRammeverk for testing i Python

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.