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