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