Pokrycie kodu Unit Test’ami- pyTest plugin: pytest-cov​

Pisząc Unit Testy, zastanawiasz się często czy przeszedłeś wszystkie wytyczone przez kod ścieżki. Plugin pytest-cov rozwiązuje ten problem.

Python Unit Test pyTest pytest-cov

Pokrycie kodu Unit Test'ami- pyTest plugin: pytest-cov


Pisząc Unit Testy używając pyTest’a, zastanawiasz się często czy przeszedłeś wszystkie wytyczone przez kod ścieżki. Otóż te przemyślenia w przypadku Pythona możesz odstawić na bok i wraz z instalacją pyTesta zainstaluj pytestcov. Dowiesz się dzięki temu, ile (procentowo) pokrywasz Unit Testami kodu, a także gdzie zostawiłeś „dziury”.

Instalacja

Instalacja pytestcov przebiega standardowo – jak większość zewnętrznych pluginów czy bibliotek Pythona). Umożliwia ją komenda pip:

pip install pytest-cov

Tak przygotowani możemy wgryzać się w świat pokrycia testami, ale żeby pisać unit testy, przydałby się kawałek kodu do testowania — co nie?

Kod do testów

Przygotujmy więc kod do testów. Intencjonalnie jest on zagnieżdżony, aby krok po kroku sprawdzać, czy odpowiednio pokryliśmy go testami. Można zastanawiać się, do czego w ogóle przydadzą nam się takie testy? Może w tym momencie wasz kod wydaje się genialny, ale z czasem każdy kod się starzeje, niepotrzebnie rozrasta i potrzebuję refactoru. Wtedy takie testy zapewnią nas, że podstawowe funkcjonalności naszego kodu zostały zachowane. Także podczas samego „rozrostu” klasy, sprawdza, czy jej podstawowe funkcjonalności nie zostały zepsute. Ot taka trochę regresja. Wracając do przykładów, stwórzmy klasę z kilkoma metodami i zagnieżdżoną w niej inną klasą.

class ExampleClass1:
    def __init__(self, arg_one, arg_two):
        self.__arg_one = arg_one
        self.__arg_two = arg_two
 
    def __str__(self):
        return str(self.__arg_one)
 
    @staticmethod
    def sum_args(first, second):
        return first + second
 
    @property
    def return_one(self):
        return self.__arg_one
 
    @property
    def return_two(self):
        return self.__arg_two
 
 
class ExampleInExample:
    def __init__(self):
        self.var_test = None
 
 
    def change_var_test(self, value):
        self.var_test = value
        return self.var_test

Teraz możemy przystąpić do pisania unit testów. Przydatnym jest rozdzielić testy od reszty kodu, np. tworząc folder unit_tests w głównym folderze z projektem. Jasno damy do zrozumienia wszystkim użytkownikom kodu gdzie umieszczać testy.

Unit testy i sprawdzanie pokrycia

Pierwszy unit test napiszmy bez dodatkowych pomocy. Tylko ty i pyTest – najlepiej dobrana para na świecie. Na początek przetestujmy inicjalizację klasy ExampleClass1. Musimy ją zaimportować do naszego pliku i wywołać w teście:

from example_code.example import ExampleClass1
 
def test_example_class_1():
    assert ExampleClass1(10, 13)

Odpal test i sprawdź, czy działa poprawnie. Jeśli wszystko jest ok czas odpalić tytułowy plugin. W najprostszy sposób dodaj po komendzie pytest atrybut –cov i nadaj mu wartość, która będzie ścieżką, do jakiego kodu ma być sprawdzone pokrycie. Moja klasa ExampleClass1 znajduje się w folderze example_code, więc właśnie to muszę wpisać, aby sprawdzić pokrycie testami znajdującego się tam kodu. Na sam koniec wybierz testy, które sprawdzają to pokrycie – w moim przypadku znajdują się one w folderze unit_tests:

pytest --cov=example_code unit_tests

Wynikiem będzie przeprowadzenie testów, ale i podsumowanie pokrycia. Jak widzicie – poszło nam całkiem nieźle. Tak prosty test sprawdza 67% kodu naszej przykładowej klasy. Stmts oznacza ilość linii do sprawdzenia, a Miss linie, które nie zostały sprawdzone.

=========================================================================================================================== test session starts ===========================================================================================================================
platform win32 -- Python 3.8.1, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: D:\test_delete
plugins: cov-2.8.1
collected 1 item
 
unit_tests\test_example.py . [100%]
 
----------- coverage: platform win32, python 3.8.1-final-0 -----------
Name Stmts Miss Cover
---------------------------------------------
example_code\example.py 21 7 67%
 
============================================================================================================================ 1 passed in 0.09s ============================================================================================================================
 
(venv) D:\test_delete>

Możemy dopasowywać testy w ciemno, sprawdzając, czy coś to zmienia lub posłużyć się naszym nowym narzędziem.

Wyświetlanie pokrycia w wygodniej fromie strony internetowej

pytest-cov umożliwia nam generowanie raportu o stanie testów w różnych formach przy użyciu argumentu –cov-report:

pytest --cov-report html
--cov-report xml
--cov-report annotate
--cov=myproj tests

Mi jednak najbardziej przypadła do gustu forma przeglądarkowa. Wygenerujmy więc stronę opisującą stan naszych unit testów:

pytest --cov-report html --cov=example_code unit_tests

Po wywołaniu powyższej komendy, w miejscu wywołania pojawi się folder: htmlcov. Znajdziecie w nim plik index.html, który należy otworzyć za pomocą dowolnej przelądarki. Ukażde się nam strona opisująca pokorycie testami:

Pozornie niewiele więcej informacji niż z poziomu linii komend. Przyciśnij example_code\example.py, a przejdziesz do kolejnej strony z masą użytecznych informacji:

Czy to nie jest niesamowite? Spytacie co takiego. Już na pierwszy rzut oka widzimy kod, który nie został wykonany podczas unit testów – część oznaczona na czerwono. Teraz wystarczy dopisać testy, które używają niewykorzystane części


Poziom trudności
3/5
Pisząc Unit Testy używając pyTest’a, zastanawiasz się często czy przeszedłeś wszystkie wytyczone przez kod ścieżki. Otóż te przemyślenia w przypadku Pythona możesz odstawić na bok i wraz z instalacją pyTest‚a zainstaluj pytest–cov. Dowiesz się dzięki temu, ile (procentowo) pokrywasz Unit Test‚ami kodu, a także gdzie zostawiłeś „dziury”.

Instalacja
Instalacja pytest–cov przebiega standardowo – jak większość zewnętrznych pluginów czy bibliotek Python‚a). Umożliwia ją komenda pip:

pip install pytest-cov
Tak przygotowani możemy wgryzać się w świat pokrycia testami, ale żeby pisać unit testy, przydałby się kawałek kodu do testowania — co nie?

Kod do testów
Przygotujmy więc kod do testów. Intencjonalnie jest on zagnieżdżony, aby krok po kroku sprawdzać, czy odpowiednio pokryliśmy go testami. Można zastanawiać się, do czego w ogóle przydadzą nam się takie testy? Może w tym momencie wasz kod wydaje się genialny, ale z czasem każdy kod się starzeje, niepotrzebnie rozrasta i potrzebuję refactoru. Wtedy takie testy zapewnią nas, że podstawowe funkcjonalności naszego kodu zostały zachowane. Także podczas samego „rozrostu” klasy, sprawdza, czy jej podstawowe funkcjonalności nie zostały zepsute. Ot taka trochę regresja. Wracając do przykładów, stwórzmy klasę z kilkoma metodami i zagnieżdżoną w niej inną klasą.

class ExampleClass1:
    def __init__(self, arg_one, arg_two):
        self.__arg_one = arg_one
        self.__arg_two = arg_two
 
    def __str__(self):
        return str(self.__arg_one)
 
    @staticmethod
    def sum_args(first, second):
        return first + second
 
    @property
    def return_one(self):
        return self.__arg_one
 
    @property
    def return_two(self):
        return self.__arg_two
 
 
class ExampleInExample:
    def __init__(self):
        self.var_test = None
 
 
    def change_var_test(self, value):
        self.var_test = value
        return self.var_test
Teraz możemy przystąpić do pisania unit testów. Przydatnym jest rozdzielić testy od reszty kodu, np. tworząc folder unit_tests w głównym folderze z projektem. Jasno damy do zrozumienia wszystkim użytkownikom kodu gdzie umieszczać testy.

Unit testy i sprawdzanie pokrycia
Pierwszy unit test napiszmy bez dodatkowych pomocy. Tylko ty i pyTest – najlepiej dobrana para na świecie. Na początek przetestujmy inicjalizację klasy ExampleClass1. Musimy ją zaimportować do naszego pliku i wywołać w teście:

from example_code.example import ExampleClass1
 
def test_example_class_1():
    assert ExampleClass1(10, 13)
Odpal test i sprawdź, czy działa poprawnie. Jeśli wszystko jest ok czas odpalić tytułowy plugin. W najprostszy sposób dodaj po komendzie pytest atrybut –cov i nadaj mu wartość, która będzie ścieżką, do jakiego kodu ma być sprawdzone pokrycie. Moja klasa ExampleClass1 znajduje się w folderze example_code, więc właśnie to muszę wpisać, aby sprawdzić pokrycie testami znajdującego się tam kodu. Na sam koniec wybierz testy, które sprawdzają to pokrycie – w moim przypadku znajdują się one w folderze unit_tests:

pytest --cov=example_code unit_tests
Wynikiem będzie przeprowadzenie testów, ale i podsumowanie pokrycia. Jak widzicie – poszło nam całkiem nieźle. Tak prosty test sprawdza 67% kodu naszej przykładowej klasy. Stmts oznacza ilość linii do sprawdzenia, a Miss linie, które nie zostały sprawdzone.

=========================================================================================================================== test session starts ===========================================================================================================================
platform win32 -- Python 3.8.1, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: D:\test_delete
plugins: cov-2.8.1
collected 1 item
 
unit_tests\test_example.py . [100%]
 
----------- coverage: platform win32, python 3.8.1-final-0 -----------
Name Stmts Miss Cover
---------------------------------------------
example_code\example.py 21 7 67%
 
============================================================================================================================ 1 passed in 0.09s ============================================================================================================================
 
(venv) D:\test_delete>
Możemy dopasowywać testy w ciemno, sprawdzając, czy coś to zmienia lub posłużyć się naszym nowym narzędziem.

Wyświetlanie pokrycia w wygodniej fromie strony internetowej
pytest-cov umożliwia nam generowanie raportu o stanie testów w różnych formach przy użyciu argumentu –cov-report:

pytest --cov-report html
--cov-report xml
--cov-report annotate
--cov=myproj tests
Mi jednak najbardziej przypadła do gustu forma przeglądarkowa. Wygenerujmy więc stronę opisującą stan naszych unit testów:

pytest --cov-report html --cov=example_code unit_tests
Po wywołaniu powyższej komendy, w miejscu wywołania pojawi się folder: htmlcov. Znajdziecie w nim plik index.html, który należy otworzyć za pomocą dowolnej przelądarki. Ukażde się nam strona opisująca pokorycie testami:

Pokrycie kodu Unit Testami  pyTest plugin: pytest cov​ image
Pozornie niewiele więcej informacji niż z poziomu linii komend. Przyciśnij example_code\example.py, a przejdziesz do kolejnej strony z masą użytecznych informacji:

Pokrycie kodu Unit Testami  pyTest plugin: pytest cov​ image
Czy to nie jest niesamowite? Spytacie co takiego. Już na pierwszy rzut oka widzimy kod, który nie został wykonany podczas unit testów. Jest to część oznaczona na czerwono. Teraz wystarczy dopisać testy, które używają niewykorzystane części

from example_code.example import ExampleClass1
 
def test_example_class_1():
    assert ExampleClass1(10, 13)
 
def test_example_class_1_str():
    assert str(ExampleClass1(10, 13)) == '10'
 
def test_example_class_1_returns():
    ec = ExampleClass1(10, 13)
    assert ec.return_one == 10
    assert ec.return_two == 13
 
def test_example_class_1_sum():
    assert ExampleClass1.sum_args(10, 13) == 23
 
def test_example_in_example():
    assert ExampleClass1.ExampleInExample()
 
def test_example_in_example_var_test():
    eie = ExampleClass1.ExampleInExample()
    eie.change_var_test("test")
    assert eie.var_test == "test"

Wywołaj jeszcze raz pytest-cov z generacją raportu w html i podziwiaj 100% pokrycia:

W dużych projektach, gdzie dodanie linijki niepowołanego kodu do metod w losowych klasach unit testy mogą okazać się kluczowym, a zarazem najtańszym zabezpieczeniem przed zablokowaniem całego projektu. Więc dobre pokrycie testami jest w takich momentach niezastąpione. Mam nadzieję, że pytest-cov uratuje wasz projekt przed kataklizmem.

close

Newsletter