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.
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.
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
Gdybyśmy uruchomili testy, bez parametryzacji, pierwszy negatywny test, przerwałby operacje testowania.
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)
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/