Pytest – czemu cieszy się większą popularnością niż unittest?
Zaktualizowaliśmy ten tekst dla Ciebie!
Data aktualizacji: 26.12.2024
Autor aktualizacji: Adam Olszewski
W mojej karierze zawodowej pisałem testy z wykorzystaniem obu frameworków: unittest oraz pytest. Oba są świetnymi narzędziami z określonymi zaletami i wadami, jednak pytest jest obecnie zdecydowanie bardziej popularny. W tym krótkim wpisie na blogu podzielę się z Wami kilkoma funkcjami pytest, które moim zdaniem stanowią odpowiedź na pytanie zawarte w tytule.
Doświadczenia z unittest w pytest – out-of-the-box
Na początek warto wspomnieć, że pytest od samego początku wspiera klasę unittest.TestCase. Wszystkie nawyki wypracowane podczas pisania testów z użyciem unittest (np. pisanie asercji) pozostają bez zmian, co zapewnia niezwykle płynne przejście do nowego frameworka. Dodatkowo, większość funkcji unittest również działa, więc wykonawca pytest może uruchamiać stare testy (z wyjątkiem subtestów, które nie są obsługiwane).
Co więcej, w podklasach unittest.TestCase działają również niektóre funkcje pytest, takie jak oznaczanie testów za pomocą markerów.
Aby uruchomić istniejące testy oparte na unittest, należy wykonać następujące polecenie:
$ pytest my_unittest_tests
Możemy również tworzyć testy, które używają unittestów oraz dodatkowo funkcjonalności pytest i współgrają one razem bez żadnych problemów:
import pytest
import unittest
class ClassTest(unittest.TestCase):
@pytest.mark.xfail
def test_feature_a(self):
self.assertEqual(2, 3)
def test_feature_b(self):
self.assertTrue(True)
Zakładając, że ten kod został zapisany w pliku run_class.py, polecenie do jego wykonania wygląda następująco:
$ pytest run_class.py
Wynik operacji:
$ pytest run.py
============================= test session starts ==========================
platform win32 -- Python 3.6.3, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: path_to_root\examples, inifile:
collected 2 items
run.py x.
[100%]
===================== 1 passed, 1 xfailed in 0.07 seconds =====================
Rozdzielanie testów na wiele CPU za pomocą xdist
Pytest oferuje możliwość uruchamiania testów równolegle za pomocą wielu procesów dzięki rozszerzeniu pytest-xdist. Im więcej testów jest do wykonania, tym większy zysk możliwy do uzyskania (nawet 2 razy szybsze działanie testów!).
Najpierw należy zainstalować wspomniany moduł, ponieważ nie jest on domyślnie dołączony do pytest. Zrobić to można uruchamiając następującą komendę:
$ pip install pytest-xdist
Aby uruchomić testy konkretnego modułu z wykorzystaniem wielu procesów, wykonaj poniższe polecenie (gdzie n oznacza liczbę równoległych procesów):
$ pytest test_class.py -n 6
Warto zwrócić uwagę na istotną kwestię związaną z czasem wykonania testów równoległych. Każdy podproces określony przez argument -n tworzy własny zestaw testów (suite), który posiada własną izolowaną sesję (w tym wspólne części wykonania pomiędzy testami oraz rozwiązywanie pliku conftest.py). Oznacza to, iż w przypadku gdy testy zawierają kosztowne operacje ładowania wykonywane na początku zestawu, czas ich wykonania zostanie pomnożony przez wartość -n. W praktyce może to zmniejszyć potencjalne korzyści możliwe do uzyskania względem czasu wykonania.
Bezproblemowa integracja z modułem parameterized
Pytest jest doceniany za doskonałą integrację z różnymi modułami. W rzeczywistości pytest oferuje testy parametryzowane „out-of-the-box”, jednak zdecydowałem się użyć modułu zewnętrznego ze względu na ograniczenia testów parametryzowanych przy dziedziczeniu po unittest.TestCase.
Aby zainstalować moduł parameterized, użyj następującego polecenia:
$ pip install parameterized
Moduł ten pozwala uruchomić konkretną metodę testową wielokrotnie, z różnymi zestawami danych testowych, stosując po prostu dekorator do metody testowej.
import requests
import unittest
from parameterized import parameterized
class TestRepositories(unittest.TestCase):
@parameterized.expand(['autorepr', 'django-nose', 'chai-subset'])
def test_repos_existence_in_wolever_profile(self, repo):
api_url = 'https://api.github.com/users/wolever/repos'
response = requests.get().json(api_url)
self.assertIn(repo, [item['name'] for item in response])
Pytest automatycznie generuje 3 metody testowe z przyrostkiem odpowiadającym wartościom repo. Nie jest potrzebny żaden dodatkowy parametr, aby uruchomić testy parametryzowane. Jasną zaletą jest możliwość wielokrotnego użycia istniejącego kodu do licznych testów.
Przykładowy wynik:
test_repos.py::TestRepositories::test_repos_existence_in_wolever_profile_0_autorepr
test_repos.py::TestRepositories::test_repos_existence_in_wolever_profile_1_django_nose
test_repos.py::TestRepositories::test_repos_existence_in_wolever_profile_2_chai_subset
Oznaczanie testów jako sposób organizacji zestawów testowych
GGłównym sposobem organizacji zestawów testowych jest umieszczanie ich w osobnych modułach, np.: client_registration, shopping, transaction itd. Co jednak w sytuacji, gdy zachodzi potrzeba zbudowania przekrojowego zestawu testów (wybór testów z kilku modułów)? Pytest proponuje jedno z rozwiązań, które uważam za elastyczne i czytelne – markery.
Załóżmy, że nasza aplikacja jest używana przez kilku klientów, którzy wdrożyli różne zestawy metod płatności. Dzięki funkcji oznaczania testów możemy łatwo określić, które testy są związane z danym klientem.
Przykładowo, załóżmy, że mamy dwa pliki pythonowe: test_auth.py i test_payments.pytest_auth.py.
import pytest
@pytest.mark.client_a
def test_authentication_of_client_a():
pass
@pytest.mark_client_b
def test_authentication_of_client_b():
pass
test_payments.py
import pytest
@pytest.mark.client_a
def test_paypal_payment():
pass
@pytest.mark.client_a
@pytest.mark.client_b
def test_credit_card_payment():
pass
@pytest.mark.client_b
def test_apple_pay_payment():
pass
Aby uruchomić tylko testy dla client_a lub client_b, należy użyć następującego polecenia:
$ pytest –m client_a
Pytest wyszukuje i wybiera testy zgodne z danym oznaczeniem, dając nam wynik:
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Repozytorium, inifile:
plugins: xdist-1.25.0, forked-0.2
collected 5 items / 2 deselected
examples\test_auth.py . [ 33%]
examples\test_payments.py .. [100%]
=================== 3 passed, 2 deselected in 0.20 seconds ====================
Przydatne rozszerzenie – flake8
Kolejną zaletę pytest stanowią liczne rozszerzenia, które integrują powszechnie używane narzędzia z frameworkiem pytest.
Instalacja rozszerzenia
Aby zainstalować rozszerzenie pytest-flake8, wykonaj następujące polecenie:
$ pip install pytest-flake8
Następnie będziemy mogli wywołać sprawdzenie flake8 w bieżącej lokalizacji za pomocą polecenia:
$ pytest --flake8
Problemy z zależnościami
Należy zauważyć, że instalacja pytest-flake8 nie zarządza wersją zależności flake8. Jeśli pojawi się problem z kompatybilnością, otrzymasz komunikat, np.:
pluggy.manager.PluginValidationError: Plugin 'flake8' could not be loaded: (setuptools 28.8.0 (c:\users\username\appdata\local\programs\python\python36\lib\site-packages), Requirement.parse('setuptools>=30'), {'flake8'})!
Powyższy komunikat informuje, że setuptools musi być w wersji 30 lub nowszej.
Konfiguracja flake8
Domyślne sprawdzenie kodu za pomocą flake8 może wygenerować setki ostrzeżeń. Dlatego istnieje opcja konfiguracji, aby dostosować reguły sprawdzania. Warto zauważyć, że konfiguracja nie jest umieszczana w pliku .flake8 (jak w przypadku flake8), lecz w pliku setup.cfg lub tox.ini w sekcji [pytest].
Przykład zawartości pliku setup.cfg:
# content of setup.cfg
[pytest]
flake8-max-line-length = 120
flake8-ignore = E201 E231
flake8-ignore = *.py E201config/app_cfg.py ALL
Przykład działania
Po dostosowaniu ustawień i rozwiązaniu błędów, testy zakończą się sukcesem:
$ pytest example/config.py --flake8
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Repozytorium, inifile:
plugins: xdist-1.25.0, forked-0.2, flake8-1.0.2
collected 1 item
example\config.py . [100%]
========================== 1 passed in 0.12 seconds ===========================
Muszę jednak ostrzec, że używanie flake8 jako hooka dla git commit może być irytujące. Na przykład, gdy na produkcji pojawi się nagły problem, a Twój hotfix nie może zostać zatwierdzony, ponieważ walczysz z błędami flake8. W innych przypadkach narzędzie to jest niezwykle pomocne w utrzymywaniu jednolitych standardów kodowania w całym projekcie.
Generowanie wyników testów w formacie HTML
Kolejnym przydatnym rozszerzeniem dostępnym dla pytest, które umożliwia generowanie raportów HTML, jest pytest-html.
Instalacja rozszerzenia
Aby zainstalować pytest-html, wykonaj następujące polecenie:
$ pip install pytest-html
Generowanie raportu HTML
Po instalacji można uruchomić testy i wygenerować raport HTML za pomocą poniższego polecenia:
$ pytest –-html=report.html
Po wykonaniu powyższego polecenia, wszystkie testy z bieżącego katalogu zostaną zebrane, wykonane, a wyniki zapisane w raporcie HTML, który będzie dostępny w bieżącym katalogu zgodnie z podanym poleceniem.
Podsumowanie
Osobiście preferuję korzystanie z pytest zamiast unittest. Jest szybki i niezawodny. Pomimo tego, że ogranicza kod szablonowy (boilerplate) do minimum, pozostaje czytelny. Chociaż pytest nie jest częścią biblioteki standardowej Pythona (co dla niektórych może być wadą), dla mnie stanowi to zaletę – nowe wersje pytest nie są uzależnione od oficjalnych wydań Pythona (które pojawiają się znacznie rzadziej).
Pytest pomaga mi lepiej organizować zestawy testowe oraz prezentować wyniki testów innym. Z całego serca polecam to narzędzie.
Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!
Skontaktuj się z nami


