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 ein kritisk del av programvareutvikling. Godt testa program har færre feil, er enklare å vedlikehalde og gir utviklarar tryggleik når dei gjer endringar. I dette kapittelet skal vi sjå på ulike typar testing – frå einingstestar av individuelle funksjonar til integrasjonstestar av heile systemet.

Vi skal også sjå på Test-Driven Development (TDD) og kva code review har å seie for kvalitetssikring.

Testpyramiden

Testpyramiden viser korleis ulike typar testar bør balanserast:

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

Einingstestar (Unit tests)

Testar éin enkelt funksjon eller metode isolert:
- Rask køyretid (millisekund)
- Enkle å skrive og vedlikehalde
- Finn bugs tidleg
- Fungerer som dokumentasjon av forventa åtferd

Bibliotek: unittest (Python standard), pytest

Integrasjonstestar

Testar samspelet mellom fleire komponentar:
- Database + backend
- API + frontend
- Fleire modular saman

End-to-End (E2E) testar

Testar heile systemet frå perspektivet til brukaren:
- Simulerer ekte brukarscenario
- Treg køyretid (sekund/minutt)
- Meir kompleks å skrive og vedlikehalde
- Gir høgast tillit til at systemet fungerer

Verktøy: Selenium, Playwright, Cypress

Test-Driven Development (TDD)

TDD er ein utviklingsmetode der testar blir skrivne før koden:

1. Red – Skriv ein test som feilar (funksjonen finst ikkje enno)
2. Green – Skriv minimal kode for å få testen til å passere
3. Refactor – Forbetre koden utan å endre åtferd

Fordelar:
- Tvingar deg til å tenkje på grensesituasjonar
- All kode er testa by design
- Enklare å refaktorere seinare

Ulemper:
- Tek lengre tid i starten
- Krev disiplin

Code Review

Code review er prosessen der andre utviklarar les og kommenterer koden din før han blir merga:

Kva ser vi etter:
- Logiske feil
- Manglande feilhandtering
- Usikker kode (SQL injection, XSS)
- Dårleg namngiving eller struktur
- Manglande testar

Best practices:
- Hald pull requests små (< 400 linjer)
- Vær konstruktiv, ikkje personangrep
- Forklar kvifor, ikkje berre kva
- Anerkjenn god kode

Testdekningsgrad (Code Coverage)

Måler kor stor prosentdel av koden som blir køyrd av testane:
- 80-90% blir rekna som godt
- 100% er sjeldan nødvendig eller praktisk
- Høg coverage garanterer ikkje kvalitet, men låg coverage er eit dårleg teikn

✏️Eksempel: Einingstesting 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

Testar 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()

Køyre testane:

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: Einingstesting med pytest

Pytest er eit populært alternativ med enklare 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

Køyre pytest:

pytest test_calculator_pytest.py -v

Fordelar med pytest:
- Enklare syntaks (berre assert, ikkje self.assertEqual)
- Betre feilmeldingar
- Parametriserte testar
- Fixtures for oppsett/nedriving

✏️Eksempel: TDD i praksis
Oppgåve: Lag ein funksjon validate_email(email) som returnerer True hvis e-post er gyldig.

Steg 1 (Red) – Skriv test som feilar:

# 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

Køyrer testane → FEIL (funksjonen finst ikkje)

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

Køyrer testane → OK

Steg 3 (Refactor) – Forbetre 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 fleire testar:

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

Køyrer testane → Oppdagar at multiple_at_signs feilar → Forbetre koden → Gjenta

Oppgåve 1 (Flerval)
Kva er hovudformålet med einingstestar?

A) Teste heile systemet frå perspektivet til brukaren
B) Teste éin enkelt funksjon isolert
C) Teste samspelet mellom database og backend
D) Teste tryggleik og yting

Oppgåve 2 (Flerval)
I kva for rekkjefølgje blir stega i Test-Driven Development (TDD) utførte?

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

Oppgåve 3
Forklar kort skilnaden mellom einingstestar og integrasjonstestar.

Oppgåve 4
Skriv ein 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 grensesituasjonar).

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

Skriv pytest-testar for denne funksjonen (inkludert grenseverdiar).

Oppgåve 6
Kva er code coverage, og kvifor er 100% coverage ikkje alltid eit meiningsfullt mål?

Oppgåve 7
Nemn tre ting du bør sjå etter når du gjer code review av ein pull request frå ein kollega.

Oppgåve 8
Forklar kort kva "Red-Green-Refactor"-syklusen i TDD tyder.

// --- Samleoppgåver ---

Samleoppgåve 1
Du har laga ei BankAccount-klasse med metodane deposit(), withdraw() og get_balance(). Withdraw skal kaste ein InsufficientFundsError hvis saldo er for låg.

a) Skriv komplette unittest-testar for alle tre metodar.
b) Inkluder minst éin test som verifiserer at rett exception blir kasta.
c) Legg til ein test for grensesituasjonen der withdraw prøver å ta ut nøyaktig saldoen.

Samleoppgåve 2
Eit team har 70% testdekningsgrad på ein Flask-webapplikasjon. Teamleiaren krev 95% coverage før lansering.

a) Forklar kvifor høg testdekningsgrad ikkje garanterer feilfri kode.
b) Kva for delar av ein webapplikasjon er mest kritiske å teste grundig?
c) Kva er skilnaden på einingstestar og end-to-end-testar for ein slik app?
d) Korleis kan teamet bruke TDD for ny funksjonalitet framover?

Oppsummering

I dette kapittelet har du lært:

- Testing: sikrar kvalitet i programvara.
- Einingstesting: testar enkeltdelar med unittest eller pytest.
- TDD: skrive test før kode.
- Kvalitetssikring: systematisk feilfinning.
- Automatisering: testar blir køyrde automatisk.

Nøkkelomgrep


OmgrepForklaring
EiningstestTest av ein enkelt kodedel
TDDTestdriven 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.