Podstawy Pytest

Ukończone

Zacznijmy testować za pomocą narzędzia Pytest. Jak wspomniano w poprzedniej lekcji, narzędzie Pytest jest wysoce konfigurowalne i może obsługiwać złożone zestawy testów. Ale nie wymaga zbyt wiele, aby rozpocząć pisanie testów. W rzeczywistości łatwiejsza struktura umożliwia pisanie testów, tym lepiej.

Na końcu tej sekcji należy mieć wszystko, czego potrzebujesz, aby rozpocząć pisanie pierwszych testów i uruchomić je przy użyciu narzędzia Pytest.

Konwencje

Przed rozpoczęciem pisania testów musimy uwzględnić niektóre konwencje testowania, na których opiera się Pytest.

Nie ma twardych reguł dotyczących plików testowych, katalogów testowych ani ogólnych układów testowania w języku Python. Znając te reguły, możesz skorzystać z automatycznego odnajdywania testów i wykonywania bez konieczności dodatkowej konfiguracji.

Testowanie katalogu i plików testowych

Głównym katalogem testów jest katalog testowy. Ten katalog można umieścić na poziomie głównym projektu, ale nie jest również niczym niezwykłym, aby zobaczyć go obok modułów kodu.

Uwaga

W tym module domyślnie użyjemy testów w katalogu głównym projektu.

Zobaczmy, jak wygląda katalog główny małego projektu w języku Python o nazwie jformat :

.
├── README.md
├── jformat
│   ├── __init__.py
│   └── main.py
├── setup.py
└── tests
    └── test_main.py

Katalog tests znajduje się w katalogu głównym projektu z jednym plikiem testowym. W tym przypadku plik testowy jest nazywany test_main.py. W tym przykładzie przedstawiono dwie konwencje krytyczne:

  • Użyj katalogu testów, aby umieścić pliki testowe i zagnieżdżone katalogi testowe.
  • Pliki testowe prefiksu z testem. Prefiks wskazuje, że plik zawiera kod testowy.

Uwaga

Unikaj używania test (formularza pojedynczego) jako nazwy katalogu. Nazwa test jest modułem języka Python, więc utworzenie katalogu o tej samej nazwie spowoduje zastąpienie go. Zawsze używaj liczby mnogiej tests .

Testowanie funkcji

Jednym z silnych argumentów dotyczących używania narzędzia Pytest jest to, że umożliwia pisanie funkcji testowych. Podobnie jak w przypadku plików testowych, funkcje testowe muszą być poprzedzone prefiksem test_. Prefiks test_ gwarantuje, że narzędzie Pytest zbiera test i wykonuje go.

Oto jak wygląda prosta funkcja testowa:

def test_main():
    assert "a string value" == "a string value"

Uwaga

Jeśli znasz element unittest, może być zaskakujące, aby zobaczyć użycie funkcji assert w funkcji testowej. Bardziej szczegółowo omówimy zwykłe potwierdzenia, ale dzięki narzędziu Pytest uzyskasz zaawansowane raportowanie błędów z prostymi potwierdzeniami.

Klasy testowe i metody testowe

Podobnie jak w przypadku konwencji dotyczących plików i funkcji, klasy testowe i metody testowe używają następujących konwencji:

  • Klasy testowe są poprzedzone prefiksem Test
  • Metody testowe są poprzedzone prefiksem test_

Podstawową różnicą w bibliotece języka unittest Python jest to, że nie ma potrzeby dziedziczenia.

W poniższym przykładzie użyto tych prefiksów i innych konwencji nazewnictwa języka Python dla klas i metod. Przedstawia on małą klasę testowania, która sprawdza nazwy użytkowników w aplikacji.

class TestUser:

    def test_username(self):
        assert default() == "default username"

Uruchom testy

Pytest jest zarówno strukturą testową, jak i modułem uruchamiającym testy. Moduł uruchamiający testy jest plikiem wykonywalnym w wierszu polecenia, który na wysokim poziomie może:

  • Wykonaj zbieranie testów, wyszukując wszystkie pliki testowe, klasy testowe i funkcje testowe dla przebiegu testu.
  • Zainicjuj przebieg testu, wykonując wszystkie testy.
  • Śledź błędy, błędy i testy z przekazywaniem.
  • Udostępnianie rozbudowanego raportowania na końcu przebiegu testu.

Uwaga

Ponieważ narzędzie Pytest jest biblioteką zewnętrzną, należy ją zainstalować, aby można było z niej korzystać.

Biorąc pod uwagę te treści w pliku test_main.py , możemy zobaczyć, jak działa narzędzie Pytest uruchamia testy:

# contents of test_main.py file

def test_main():
    assert True

W wierszu polecenia w tej samej ścieżce, w której istnieje plik test_main.py , możemy uruchomić pytest plik wykonywalny:

 $ pytest
=========================== test session starts ============================
platform -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private/tmp/project
collected 1 item

test_main.py .                                                       [100%]

============================ 1 passed in 0.00s =============================

W tle narzędzie Pytest zbiera przykładowy test w pliku testowym bez konieczności jakiejkolwiek konfiguracji.

Zaawansowana instrukcja asercji

Do tej pory nasze przykłady testów używają zwykłego assert wywołania. Zwykle w języku Python instrukcja assert nie jest używana do testów, ponieważ nie ma odpowiedniego raportowania, gdy asercji kończy się niepowodzeniem. Jednak narzędzie Pytest nie ma tego ograniczenia. W tle narzędzie Pytest umożliwia instrukcji wykonywanie zaawansowanych porównań bez zmuszania użytkownika do pisania większej ilości kodu lub konfigurowania niczego.

Używając zwykłej assert instrukcji, można używać operatorów języka Python. Na przykład: >, , !=<, >=, lub <=. Wszystkie operatory języka Python są prawidłowe. Ta funkcja może być jedną najważniejszą funkcją narzędzia Pytest: nie musisz uczyć się nowej składni do pisania asercji.

Zobaczmy, jak to tłumaczy się podczas pracy z typowymi porównaniami z obiektami języka Python. W tym przypadku przejmijmy raport o błędach podczas porównywania długich ciągów:

================================= FAILURES =================================
____________________________ test_long_strings _____________________________

    def test_long_strings():
        left = "this is a very long strings to be compared with another long string"
        right = "This is a very long string to be compared with another long string"
>       assert left == right
E       AssertionError: assert 'this is a ve...r long string' == 'This is a ve...r long string'
E         - This is a very long string to be compared with another long string
E         ? ^
E         + this is a very long strings to be compared with another long string
E         ? ^                         +

test_main.py:4: AssertionError

Narzędzie Pytest pokazuje przydatny kontekst wokół błędu. Nieprawidłowa wielkość liter na początku ciągu i dodatkowy znak w słowie. Jednak poza ciągami narzędzie Pytest może pomóc w pracy z innymi obiektami i strukturami danych. Na przykład poniżej przedstawiono sposób jego działania z listami:

________________________________ test_lists ________________________________

    def test_lists():
        left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
        right = ["sugar", "coffee", "wheat", "salt", "water", "milk"]
>       assert left == right
E       AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'co...ater', 'milk']
E         At index 1 diff: 'wheat' != 'coffee'
E         Full diff:
E         - ['sugar', 'coffee', 'wheat', 'salt', 'water', 'milk']
E         ?                     ---------
E         + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E         ?           +++++++++

test_main.py:9: AssertionError

Ten raport identyfikuje, że indeks 1 (drugi element na liście) jest inny. Nie tylko identyfikuje numer indeksu, ale także przedstawia reprezentację błędu. Oprócz porównań elementów można również zgłaszać, czy brakuje elementów, i podać informacje, które mogą powiedzieć dokładnie, który element może być. W następującym przypadku:"milk"

________________________________ test_lists ________________________________

    def test_lists():
        left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
        right = ["sugar", "wheat", "salt", "water", "milk"]
>       assert left == right
E       AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'wh...ater', 'milk']
E         At index 2 diff: 'coffee' != 'salt'
E         Left contains one more item: 'milk'
E         Full diff:
E         - ['sugar', 'wheat', 'salt', 'water', 'milk']
E         + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E         ?                    ++++++++++

test_main.py:9: AssertionError

Na koniec zobaczmy, jak działa z słownikami. Porównywanie dwóch dużych słowników może być przytłaczające, jeśli występują błędy, ale Pytest wykonuje wybitne zadanie w kontekście i wskazuje błąd:

____________________________ test_dictionaries _____________________________

    def test_dictionaries():
        left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
        right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
>       assert left == right
E       AssertionError: assert {'county': 'F...rry Ln.', ...} == {'county': 'F...ry Lane', ...}
E         Omitting 3 identical items, use -vv to show
E         Differing items:
E         {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E         {'number': 39} != {'number': 38}
E         Full diff:
E           {
E            'county': 'Frett',...
E
E         ...Full output truncated (12 lines hidden), use '-vv' to show

W tym teście w słowniku występują dwa błędy. Jedną z nich jest to, że "street" wartość jest inna, a druga jest "number" niezgodna.

Narzędzie Pytest dokładnie wykrywa te różnice (mimo że jest to jeden błąd w jednym teście). Ponieważ słowniki zawierają wiele elementów, narzędzie Pytest pomija identyczne części i pokazuje tylko odpowiednią zawartość. Zobaczmy, co się stanie, jeśli użyjemy sugerowanej -vv flagi, aby zwiększyć szczegółowość danych wyjściowych:

____________________________ test_dictionaries _____________________________

    def test_dictionaries():
        left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
        right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
>       assert left == right
E       AssertionError: assert {'county': 'Frett',\n 'number': 39,\n 'state': 'Nevada',\n 'street': 'Ferry Ln.',\n 'zipcode': 30877} == {'county': 'Frett',\n 'number': 38,\n 'state': 'Nevada',\n 'street': 'Ferry Lane',\n 'zipcode': 30877}
E         Common items:
E         {'county': 'Frett', 'state': 'Nevada', 'zipcode': 30877}
E         Differing items:
E         {'number': 39} != {'number': 38}
E         {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E         Full diff:
E           {
E            'county': 'Frett',
E         -  'number': 38,
E         ?             ^
E         +  'number': 39,
E         ?             ^
E            'state': 'Nevada',
E         -  'street': 'Ferry Lane',
E         ?                    - ^
E         +  'street': 'Ferry Ln.',
E         ?                     ^
E            'zipcode': 30877,
E           }

Uruchamiając polecenie pytest -vv, raportowanie zwiększa ilość szczegółów i zapewnia szczegółowe porównanie. Nie tylko ten raport wykrywa i pokazuje błąd, ale umożliwia szybkie wprowadzanie zmian w celu rozwiązania problemu.