Podstawy testów jednostkowych w Pythonie cz.1

W celu sprawdzenia poprawności napisanego kodu możemy napisać testy sprawdzające przy użyciu modułu unittest. Raz napisane testy pomagają również przy późniejszych zmianach w napisanym kodzie – czy zmiany nie spowodowały powstania błędów. Możemy również posłużyć się techniką TDD (Test Driven Development), w której pisanie testów poprzedza tworzenie właściwego programu.

Przedstawię klasę testującą poprawność metod obecnych w klasie Employee modułu employee.py. Prosta, przykładowa klasa Employee będzie zawierała oprócz metody __init__() także alternatywny konstruktor (metoda klasowa) oraz metody: set_bonus() – ustawianie premii dla pracownika, get_full_salary() – wynagrodzenie (podstawa+premia) oraz get_email() – adres email pracownika. W tym celu tworzymy skrypt zawierający klasę dziedziczącą po unittest.TestCase. Skrypt oczywiście musi zaimportować moduł unittest oraz testowany moduł tzn. listing pliku test_employee.py będzie wyglądał następująco:

import unittest
from employee import Employee

class EmployeeTestCase(unittest.TestCase):
    def setUp(self):
        self.john_doe = Employee('John', 5000)
        self.jane_doe = Employee.from_full_name('Jane', 'Doe', 6000)

    def test_get_email(self):
        self.assertEqual(self.john_doe.get_email(), 'John@company.com')
        self.assertEqual(self.jane_doe.get_email(), 'Jane.Doe@company.com')

    def test_get_full_salary(self):
        self.assertEqual(self.john_doe.get_full_salary(), 5000)
        self.assertEqual(self.jane_doe.get_full_salary(), 6000)
        self.john_doe.set_bonus(10)
        self.jane_doe.set_bonus(15)
        self.assertEqual(self.john_doe.get_full_salary(), 5500)
        self.assertEqual(self.jane_doe.get_full_salary(), 6900)

Aby uruchomić poprawnie powyższy skrypt testujący należy go załadować jako moduł tzn.

python -m unittest test_employee.py

lub też dołączyć do skryptu testującego wywołanie unittest.main() tzn.

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

>>część druga<<

Podstawy testów jednostkowych w Pythonie cz.2

Na podstawie napisanego skryptu testującego możemy napisać plik employee.py tzn.

class Employee():
    bonus = 0

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def get_email(self):
        pass

    def set_bonus(self, bonus):
        pass

    def get_full_salary(self):
        pass

    @classmethod
    def from_full_name(cls, first_name, last_name, salary):
        name = '{}.{}'.format(first_name, last_name)
        return cls(name, salary)

Tak utworzony zrąb klasy Employee po uruchomieniu testów generuje 2 niepowodzenia w przeprowadzonych testach (pomimo obecności kilku asercji w jednym teście). Uzupełnienie metod klasy Employee spowoduje poprawne przejście 2 testów tzn.

class Employee():
    bonus = 0

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def get_email(self):
        return '{}@company.com'.format(self.name)

    def set_bonus(self, bonus):
        self.bonus = bonus

    def get_full_salary(self):
        return self.salary + self.salary * self.bonus / 100

    @classmethod
    def from_full_name(cls, first_name, last_name, salary):
        name = '{}.{}'.format(first_name, last_name)
        return cls(name, salary)

Oprócz sprawdzania równości assertEqual(x, y) możemy sprawdzać różnego rodzaju asercje np.

  • assertNotEqual(x, y) #sprawdza, czy x != y
  • assertIsNone(x) #sprawdza, czy x jest wartością None
  • assertTrue(x) #sprawdza czy bool(x) jest wartością True

Metoda setUp() z modułu unittest jest uruchamiana każdorazowo przed wykonaniem kolejnego testu. Podobnie istnieje metoda tearDown(), która jest wykonywana po zakończeniu każdego testu.

Istnieją również metody klasowe setUpClass() oraz tearDownClass(), które są uruchamiane przed rozpoczęciem i po zakończeniu wszystkich testów np. w celu zainicjowania a następnie zamknięcia połączenia z bazą danych.