Основи Pytest

Завершено

Почнімо тестування з Pytest. Як ми згадували в попередньому блоці, Pytest дуже настроюється і може обробляти складні тестові комплекси, але це не вимагає багато, щоб почати написання тестів. Насправді, що простіше інфраструктура дозволяє писати тести, тим краще.

До кінця цього розділу ви повинні мати все необхідне, щоб почати писати свої перші тести і запускати їх за допомогою Pytest.

Конвенцій

Перш ніж пірнати в тести для письма, ми повинні охопити деякі конвенції тестування, на які спирається Pytest.

Існує не так багато жорстких правил щодо тестових файлів, тестових каталогів або загальних макетів тестування в Python. Знаючи ці правила, ви можете скористатися перевагами автоматичного виявлення та виконання тестів без додаткової конфігурації.

Перевірка каталогу та тестування файлів

Основний каталог тестів – це тестів каталогу. Цей каталог можна розмістити на кореневому рівні проекту, але також не дивно бачити його поряд із модулями коду.

Примітка

У цьому модулі ми використовуватимемо тести в корені проекту.

Розгляньмо, як виглядає корінь невеликого проекту Python з іменем jformat:

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

перевіряє, каталог перебуває в корені проекту з одним тестовим файлом. У цьому випадку тестовий файл називається test_main.py. У цьому прикладі показано дві критичні конвенції:

  • Використовуйте тестів каталог, щоб розміщувати тестові файли та вкладені тестові каталоги.
  • Перевірка файлів префікса за допомогою тестових. Префікс указує на те, що файл містить тестовий код.

Обережність

Уникайте використання test (однині форми) як імені каталогу. Ім'я test – це модуль Python, тому створення каталогу з таким ім'ям замінить його. Завжди використовуйте множину tests.

Тестові функції

Вагомий аргумент для використання Pytest полягає в тому, що він дає змогу писати тестові функції. Як і тестові файли, тестові функції мають бути префіксовані за допомогою test_. Префікс test_ гарантує, що Pytest збирає тест і виконує його.

Ось як виглядає проста тестова функція:

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

Примітка

Якщо ви знайомі з unittest, може бути дивно бачити використання assert в тестовій функції. Ми розглянемо прості твердження більш детально пізніше, але з Pytest, ви отримуєте широкі звіти про помилки з простими твердженнями.

Тестові класи та методи перевірки

Як і в конвенціях щодо файлів і функцій, для тестових класів і методів перевірки використовуються такі умови:

  • Тестові класи префіксуються Test
  • Методи перевірки префіксуються за допомогою test_

Основна відмінність бібліотеки unittest Python полягає в тому, що успадкування відсутнє.

У наведеному нижче прикладі використовуються ці префікси та інші правила іменування Python для класів і методів. Він демонструє невеликий клас тестування, який перевіряє імена користувачів у програмі.

class TestUser:

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

Запуск тестів

Pytest є як тестовим, так і тестовим бігуном. Тестовий бігун – це виконуваний файл у командному рядку, який (на високому рівні) може:

  • Виконайте збирання тестів, знайшовши всі тестові файли, тестові класи та тестові функції для тестового запуску.
  • Ініціювати перевірку, виконавши всі перевірки.
  • Відстежуйте помилки, помилки та проходячи тести.
  • Надайте широкі звіти наприкінці тестового запуску.

Примітка

Оскільки Pytest – це зовнішня бібліотека, її потрібно інсталювати, щоб використовувати її.

З огляду на такий вміст у файлі test_main.py, ми можемо побачити, як Pytest працює під час виконання тестів:

# contents of test_main.py file

def test_main():
    assert True

У командному рядку в тому самому шляху, де існує файл test_main.py, можна запустити виконуваний файл pytest:

 $ 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 =============================

У фоновому режимі Pytest збирає зразок тесту в тестовому файлі без будь-якої потрібної конфігурації.

Потужна заява про твердження

Поки що в наших тестових прикладах використовується звичайний виклик assert. Зазвичай у Python інструкція assert не використовується для тестів, оскільки їй не вистачає належного звітування, коли твердження не вдається виконати. Однак Pytest не має цього обмеження. За лаштунками функція Pytest дає змогу оператору виконувати широкі порівняння, не змушуючи користувача писати більше коду або нічого не настроювати.

Використовуючи звичайну assert інструкцію, можна використовувати оператори Python, наприклад , >, <!=, , >=або <=. Усі оператори Python припустимі. Ця можливість може бути однією з найважливіших функцій Pytest: ви не зобов'язані вивчати новий синтаксис для написання тверджень.

Давайте розглянемо, як це перекладається під час роботи зі спільними порівняннями з об'єктами Python. У цьому випадку давайте розглянемо звіт про помилку під час порівняння довгих рядків:

================================= 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

Pytest відображає корисний контекст навколо помилки: неправильний корпус на початку рядка та додатковий символ у слові. Але крім рядків, Pytest може допомогти з іншими об'єктами та структурами даних. Наприклад, ось як вона працює зі списками:

________________________________ 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

Цей звіт визначає, що індекс 1 (другий елемент у списку) відрізняється. Він не тільки визначає номер індексу, а й відображає помилку. Крім порівняння елементів, він також може звітувати про відсутність елементів і надавати відомості, які можуть точно визначити, який елемент може бути. У такому випадку "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

Нарешті, давайте подивимося, як вона працює зі словниками. Порівняння двох великих словників може бути неабияким, якщо сталася помилка, але Pytest виконує непогашену роботу з надання контексту та визначення помилки:

____________________________ 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

У цьому тесті є дві помилки у словнику. Одна з них полягає в тому, що значення "street" відрізняється, а інше – "number" не збігається.

Pytest точно виявляє ці відмінності (навіть якщо це одна помилка в одному тесті). Оскільки словники містять багато елементів, функція Pytest не позначає однакові частини та відображає лише відповідний вміст. Давайте розглянемо, що станеться, якщо ми скористаємося запропонованою позначкою -vv, щоб збільшити словесне наповнення у виводі:

____________________________ 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           }

Запустивши pytest -vv, звіт збільшує обсяг деталізації та забезпечує деталізовані порівняння. Цей звіт не тільки виявляє та показує помилку, але й дає змогу швидко вносити зміни, щоб виправити проблему.