Introduktion till enhetstester

By . Latest revision .

Enhetstester, eller unittester, används för att testa att enskilda metoder eller funktioner gör vad vi förväntar oss. Till exempel om en metod ska returnera bool-värdet True, så ska den aldrig kunna returnera False.

Vi ska titta lite på pythons inbyggda testramverk unittest och hur en katalogstruktur med kod och enhetstester kan se ut. Vill du läsa mer kan du kika på docs.python.org.

#Förutsättning

Du kan grunderna i Python och du vet vad variabler, typer och funktioner innebär. Du har även gjort övningen “Intro till guiden” och “Objekt och klasser” i guiden “Kom igång med objektorienterad programmering i Python”.

#Pythons testramverk

Python kommer med en inbyggd modul, ett ramverk kallat “unittest”. Inspirationskällan till det kommer från Javans JUnit. Vi ska framför allt titta på basklassen TestCase som tar hand om enskilda tester på bland annat metoder.

#Kodstruktur med enhetstester

Använd terminalen och ställ dig i “kmom01”:

$ mkdir unittest
$ mkdir unittest/src
$ mkdir unittest/tests
$ touch unittest/src/__init__.py unittest/src/car.py
$ touch unittest/test.py
$ touch unittest/tests/__init__.py unittest/tests/test_car.py
Filstruktur.

Filstruktur.

Kodstrukturen:

  • /src med källkoden, filen med klassen Car
  • /tests med koden för enhetstester, alla börjar med test_
  • test.py är en testprogrammet som kör alla testfilerna i katalogen /tests tack vare filen “/tests/__init__.py” som gör att alla testfilerna hittas i /tests. Vill du läsa mer om “__init__.py” kan du kika på docs.python.org.

#Klassen som ska testas

Lägg koden för klassen Car i /src/car.py.

#!/usr/bin/env python3
"""
Contains the Car class.
"""
class Car():
    """ Car class that handles cars with model and price. """
    wheels = 4

    def __init__(self, model, price):
        """ Constuctor """
        self.model = model
        self.price = price

    def present_car(self):
        """ Returns a string presenting the car """
        return "This car is of model {m} and costs {p}$.".format(
            m=self.model, p=self.price)

#Skapa testprogrammet

Lägg följande kod i test.py som är testprogrammet och kör alla testfiler som ligger i /tests.

#!/usr/bin/env python3
"""
Main program for testing
"""
import unittest

if __name__ == '__main__':
    testsuite = unittest.TestLoader().discover('tests')
    unittest.TextTestRunner(verbosity=2).run(testsuite)

Flytta dig till unittest och kör följande

$ python3 test.py
Resultat från python3 test.py.

Resultat från python3 test.py.

#Kom igång med ett enhetstest

Lägg följande kod i /tests/test_car.py. Observera att vägen till filen car.py pekas ut genom “src.car”.

#!/usr/bin/env python3
""" Module for testing the class Car """

import unittest
from src.car import Car

class TestCar(unittest.TestCase):
    """ Submodule for unittests, derives from unittest.TestCase """
    pass

Kör enhetstesterna igen.

$ python3 test.py
Resultat från python3 test.py.

Resultat från python3 test.py.

#Kom igång med ett enhetstest

Då ska vi lägga till en metod i testklassen TestCar i /tests/test_car.py som testar att antalet hjul på en instans vi skapar av klassen Car är 4. Byt ut “pass” mot följande kod.

def test_no_of_wheels_ok(self):
    """ Test if number of wheels is 4 """
    my_car = Car("Volvo", 50000) # Act
    self.assertEqual(my_car.wheels, 4)# Assert

Vi använder metoden assertEqual för att jämföra om två värden är lika. Följande tabell är hämtad från docs.python.org och visar överskådligt de vanligaste typerna av assert som finns för att säkerställa olika värden. De metoderna finns in basklassen, TestCase.

Method Checks that
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)

Om vi nu kör testet får vi utskriften:

python3 test.py
Resultat från python3 test.py.

Resultat från python3 test.py.

#Testa en metod

Då ska vi lägga till en ny metod i testklassen TestCar i /tests/test_car.py som testar att metoden “present_car” ger rätt resultat för en Volvo som kostar 50000$. Lägg till följande kod.

def test_present_car_ok(self):
    """ Test if the string is correct from present_car """
    my_car = Car("Volvo", 50000) # Act
    self.assertEqual(my_car.present_car(),
        "This bike is of model Volvo and costs 50000$.")# Assert

Resultat från python3 test.py.

Resultat från python3 test.py.

Aj, då det blev fel! Ändra från “bike” till “car” och testa igen.

Resultat från python3 test.py.

Resultat från python3 test.py.

#Testa med slumptal

När vi enhetstestar med slumptal, så vill vi inte behöva ändra i testfallet varje gång vi kör testen. Med hjälp av “random.seed()” så får vi samma slumpvärde varje gång vi kör testen.

import random

random.seed("car")
print("First number: ", random.randint(1, 100))
print("Second number: ", random.randint(1, 100))

Här blir första slumptalet 11 och andra slumptalet 46 varje gång. Testa gärna!

#Avslutningsvis

Detta var bara en kort introduktion om enhetstester i pythons unittest. Längre fram i kursen dyker vi ner mer i enhetstester.

Det är viktigt med tester för att upprätthålla kvalitet i koden. Det finns företag som kör med testdriven utveckling. Då utvecklar man först enhetstesterna utifrån kraven som finns på koden. Därefter utvecklas den vanliga koden och du kan testa din kod under utveckling.

#Revision history

  • 2022-01-12: (A, grm) Första versionen, utgått från “unittest_i_python”.

Document source.

Category: oopython.