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

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

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ą 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ą.

  1. class ExampleClass1:
  2. def __init__(self, arg_one, arg_two):
  3. self.__arg_one = arg_one
  4. self.__arg_two = arg_two
  5. def __str__(self):
  6. return str(self.__arg_one)
  7. @staticmethod
  8. def sum_args(first, second):
  9. return first + second
  10. @property
  11. def return_one(self):
  12. return self.__arg_one
  13. @property
  14. def return_two(self):
  15. return self.__arg_two
  16. class ExampleInExample:
  17. def __init__(self):
  18. self.var_test = None
  19. def change_var_test(self, value):
  20. self.var_test = value
  21. 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:

  1. from example_code.example import ExampleClass1
  2. def test_example_class_1():
  3. 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

  1. from example_code.example import ExampleClass1
  2. def test_example_class_1():
  3. assert ExampleClass1(10, 13)
  4. def test_example_class_1_str():
  5. assert str(ExampleClass1(10, 13)) == '10'
  6. def test_example_class_1_returns():
  7. ec = ExampleClass1(10, 13)
  8. assert ec.return_one == 10
  9. assert ec.return_two == 13
  10. def test_example_class_1_sum():
  11. assert ExampleClass1.sum_args(10, 13) == 23
  12. def test_example_in_example():
  13. assert ExampleClass1.ExampleInExample()
  14. def test_example_in_example_var_test():
  15. eie = ExampleClass1.ExampleInExample()
  16. eie.change_var_test("test")
  17. assert eie.var_test == "test"

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

Pokrycie kodu Unit Testami  pyTest plugin: pytest cov​ image

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.

Dodaj komentarz