Python jest językiem programowania, który posiada czytelną i przejrzystą strukturę w postaci bloków i wcięć oraz intuicyjne słowa kluczowe.

Parametryzacja testu i klasy w Pythonie

W pierwszej części artykułu „Python w pracy testera oprogramowania” opisałam zmienne i funkcje oraz napisałam dla Ciebie pierwszy test. Dziś napiszę kolejne testy. Skorzystam w nich z fixture'ów oraz pokażę Ci, jak wygląda parametryzacja testu. Omówimy też klasy w Pythonie. Kod napiszę w PyCharmie. Zaczynamy!

@pytest.fixture, czyli fixture

Spójrz na poniższe testy:  
 

import pytest
def test_Love():
   love = 'Love'
   assert love == 'Love'
def test_Python():
   python = 'Python'
   assert python == 'Python'
def test_LovePython():
   love = 'Love'
   python = 'Python'
   assert love == 'Love', python == 'Python'

Każdy test wymaga od Ciebie podania zmiennych, nawet jeśli zostały one zdefiniowane już wcześniej. Chcąc skrócić kod i nie powielać tych samych danych możesz zastosować fixture. Fixture to funkcja, która przygotowuje dane do testów. Można skorzystać z niego jedynie po zainstalowaniu paczki pytest. Jeśli potrzebujesz pomocy w pobraniu paczki, zerknij do pierwszej część artykułu. Funkcję, która jest fixture’em poprzedza dekorator @pytest.fixture

@pytest.fixture
def Love():
   return 'Love'
@pytest.fixture
def Python():
   return 'Python'

Z fixture'a możemy skorzystać przez podanie jego nazwy, w tym przypadku Love lub Python, w miejscu parametru funkcji testowej. Parametry podajemy w nawiasie, po nazwie testu.

def test_Love2(Love):
   assert Love == 'Love'
def test_LovePython2(Love,Python):
   assert Love == 'Love', Python == 'Python'

Do czego jeszcze używamy fixture'y?

Fixture'y poza skróceniem kodu, ułatwiają jego modyfikację w przyszłości. Wyobraź sobie test strony internetowej. Wykonujesz na niej różne operacje. Za każdym razem uruchamiasz i zamykasz stronę. Kod odpowiedzialny za operacje powtarzane, np. uruchamianie i zamykanie przeglądarki, umieszczasz wówczas w fixture'rze. Jeśli zachodzi konieczność zmiany, wprowadzasz ją w fixture'ach, nie musisz modyfikować każdego testu.

Conftest.py

Jeśli chcesz skorzystać z utworzonych fixture'ów w innym pliku testowym, przenieś je do pliku o nazwie conftest.py. Dobra praktyka nakazuje trzymanie testów i fixture'ów w różnych plikach. Python najpierw sprawdzi, czy fixture znajduje się w bieżącym pliku, a następnie zajrzy do pliku o nazwie conftest.py

@pytest.mark.parametrize, czyli parametryzacja testu

Parametryzacja polega na uruchomieniu jednego testu z różnymi zestawami argumentów. Zestawy argumentów to dane wejściowe i wyjściowe. W przypadku błędnego zestawu danych proces testowy nie zostanie przerwany.

@pytest.mark.parametrize(
"x,y,result",
   [
       (1, 2, 2),
       (4, 4, 16),
       (5, 10, 50),
       (8, 11, 88)
   ]
)
def test_mul(x, y, result):
   assert x * y == result

Parametryzację rozpoczyna dekorator @pytest.mark.parametrize, następnie w cudzysłowie umieszczamy parametry x, y, result a w nawiasach argumenty, czyli konkretne zestawy cyfr, które chcemy przetestować. Operacja, jaką na nich wykonamy to mnożenie. Zerknij poniżej na kod i efekt testu.unnamed-8.jpgunnamed-1.jpg

Testy przeszły pomyślnie, ponieważ zestawy argumentów były prawidłowe, 1*2=2, 4*4=16 itd. Ważne jest natomiast to, jak wygląda nasz kod. Moglibyśmy napisać go w jednej linii i działałby, ale dla przejrzystości i lepszej czytelności przyjęty został zapis widoczny powyżej.

Napiszę teraz dla Ciebie najprostszą klasę i na jej podstawie przeprowadzę parametryzację. Więcej o klasach opowiem Ci po omówieniu parametryzacji.

import pytest

class AddAndMin:
   def add(self, a, b):
       return a+b
   def min(self, a, b):
       return a-b

 

class

słowo kluczowe, które rozpoczyna klasę.

AddAndMin:

w naszym przypadku to nazwa klasy. Nazwę klasy oraz wszystkie słowa, z których składa się nazwa, piszemy dużą literą. Tworząc nazwę klasy, nie używamy spacji i znaków podkreślenia.

def add(self, a, b) 

    return a+b 

to funkcja. Funkcje w klasie nazywają się METODAMI. Od funkcji, istniejącej poza klasą, metodę wyróżnia self. Self to parametr metody, umożliwiający jej wywołanie. Każda metoda w klasie przyjmuje self jako pierwszy parametr.

Spójrz na test bez parametryzacji. Testujemy metodę dodawania, z klasy AddAndMin

def test_add():
add_and_min = AddAndMin()
assert add_and_min.add(1, 2) == 3
assert add_and_min.add(2, 4) == 6
assert add_and_min.add(3, 6) == 9
assert add_and_min.add(4, 8) == 12

Zauważ, że:

  • klasę zapisałam do zmiennej add_and_min i odwołałam się do metody add, podając jej nazwę i argumenty po kropce, w naszym przypadku add_and_min.add(1,2)
  • asercje na 4 zestawy danych możemy zastąpić parametryzacją testu
@pytest.mark.parametrize(
   "a, b, result",
   [
       (1, 2, 3),
       (2,4,6),
       (3,6,3),
       (4,8,12)
   ]
)

Poniżej test dla wszystkich zestawów:

def test_add_mark_parametrize(a, b, result):
   add_and_min = AddAndMin()
   assert add_and_min.add(a, b) == result

Zauważ, że zestaw danych (3,6,3) był niepoprawny. Test przejdzie pozytywnie z zestawem (3,6,9), ponieważ 3+6=9.

unnamed-9.jpg

Przetestujmy teraz drugą metodę w klasie, odejmowanie.

@pytest.mark.parametrize(
   "a, b, result",
   [
       (1, 2, - 1),
       (2,4,-2),
       (3,6,3),
       (4,8,-4)
   ]
)

Zmianie uległ wynik działania, czyli trzecia cyfra w każdym zestawie danych.

def test_min_mark_parametrize(a, b, result):
   add_and_min = AddAndMin()
   assert add_and_min.min(a, b) == result

Ponownie zestaw (3,6,3) okazał się zestawem niepoprawnym. Właściwe dane to (3,6,-3), ponieważ 3-6=-3

unnamed-3.jpg

Gdybyśmy uruchomili testy, bez parametryzacji, pierwszy negatywny test, przerwałby operacje testowania.

unnamed-6.jpg

Więcej o klasach w Pythonie

Klasa jest projektem, szablonem do tworzenia obiektów. Z jednej klasy może powstać wiele obiektów. 

class Cat:
   cat_species = 'domestic'
 
  def __init__(self, name):
       self.name = name
  
 def vocal_sound_made_by_a_cat (self):
       voice = 'meow'
       return voice
# Pierwszy obiekt o nazwie Garfield
cat_1 = Cat("Garfield")
print(cat_1.name)
# Drugi obiekt o nazwie Mruczek
cat_2 = Cat("Mruczek")
print(cat_2.name)

Pierwszą metodą klasy jest konstruktor __init__. Tworzymy go za pomocą dwóch znaków podkreślenia, słowa init i ponownie dwóch znaków podkreślenia. Konstruktor jest uruchamiany przy tworzeniu obiektu. To w nim zdefiniowane są parametry, jakie będzie miał każdy obiekt. W naszym przypadku będzie to imię. 

Tworzenie obiektu

nowa_zmienna = NazwaKlasy()

Obiekt tworzymy, przypisując nowej zmiennej nazwę klasy. Po nazwie znajduje się nawias. Nawias, a dokładnie pierwszy parametr, czyli self, wywołuje metodę tworząca obiekt, uruchamia konstruktor. Tworząc obiekt, odwołujemy się jedynie do parametru name. 

Pierwszy obiekt

cat_1 = Cat("Garfield")

print(cat_1.name)

Drugi obiekt

cat_2 = Cat("Mruczek")

print(cat_2.name)

Stworzyliśmy dwa obiekty, korzystając z jednej klasy. 

Jeśli chcemy dostać się do metody w klasie definiujemy drugą linię kodu

nowa_zmienna.nazwa_metody_w_klasie()

Najpierw przywołujemy nasz obiekt. Kropka jest odwołaniem do metod i parametrów obiektu. Po niej określamy nazwę metody, do której chcemy się odwołać. W nawiasie podajemy wymagane argumenty. Korzystając z klasy powyżej, odwołanie wyglądałoby następująco:

cat_1.vocal_sound_made_by_a_cat()
print(cat_1.vocal_sound_made_by_a_cat())

Nawias pozostanie pusty, ponieważ jedynym parametrem metody jest self, pomijany podczas wywoływania. 

Metody mogą zawierać zmienne. Do takiej zmiennej dostajemy się następującym kodem

nowa_zmienna.nazwa_zmiennej_w_klasie = 'string',

czyli obiekt, kropka oraz nazwa zmiennej. W naszym przypadku:  
 

cat_1.voice = 'meow'
print(cat_1.voice)

Zmienna może także istnieć w klasie, poza metodą. Odwołujemy się do niej, podając nazwę klasy i po kropce nazwę zmiennej.

NazwaKlasy.nazwa_zmiennej

Przy takim odwołaniu nie powstaje obiekt.

print(Cat.cat_species)

unnamed-2.jpg

Podsumowanie

Gratulacje! Potrafisz już pisać testy, z użyciem frameworka pytest, wiesz, do czego służy fixture i jak parametryzuje się testy. Znasz też klasy i metody w Pythonie.

Ciekawostka

Dlaczego użyłam apostrof w słowie fixture’a, a nie użyłam go w nazwie Pythona czy PyCharma? Odpowiedź znajdziesz w artykule https://www.monikawysocka.pl/2021/04/17/jak-odmieniac-zapozyczenia/