Podstawy Pytest
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.