Основы Pytest

Завершённый

Давайте приступим к тестированию с Pytest. Как упоминалось в предыдущем уроке, Pytest очень настраивается и может обрабатывать сложные наборы тестов, но для начала написания тестов не требуется много. На самом деле, чем проще писать тесты на платформе, тем лучше.

К концу этого раздела вам потребуется все, что вам нужно, чтобы начать писать первые тесты и запускать их с помощью Pytest.

Соглашения

Прежде чем углубляться в написание тестов, мы должны изучить некоторые соглашения о тестировании, на которые опирается Pytest.

В Python не существует много сложных правил для тестовых файлов, каталогов тестов или общих макетов тестирования. Зная эти правила, вы можете воспользоваться преимуществами автоматического обнаружения и выполнения тестов без дополнительной настройки.

Каталог тестов и тестовые файлы

Основным каталогом для тестов является каталог тестов . Этот каталог можно разместить на корневом уровне проекта, но он также не является необычным, чтобы увидеть его вместе с модулями кода.

Примечание.

В этом модуле мы по умолчанию будем использовать тесты в корне проекта.

Давайте посмотрим, как выглядит корневой каталог небольшого проекта jformat Python:

.
├── 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_

Основное отличие библиотеки Python unittest заключается в том, что не требуется наследование.

В следующем примере используются эти префиксы и другие соглашения об именовании 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отчеты увеличивают объем деталей и обеспечивают детализированное сравнение. Не только этот отчет обнаруживает и показывает сбой, но позволяет быстро вносить изменения для устранения проблемы.