Привет, друзья!
Сегодня я хочу рассказать о pytest и о том, как с ним начать работать. Сам когда-то начинал и столкнулся со множеством сложностей, но теперь я готов поделиться своим опытом.
Pytest
Pytest — это первое, с чем сталкивается любой тестировщик, который хочет начать автоматизировать и развиваться в этой области. Многие компании строят автоматизацию на Pytest, и на собеседованиях требуют его понимания. Поэтому, чтобы устроиться в такую компанию, нужно изучить Pytest.
Давайте разберёмся, что умеет этот фреймворк, на примере простых конструкций, которые часто советуют тестировщикам.
Вот код, с которым мы будем работать. Писать и запускать его будем в GigaIDE.
# myProject/cashReceipt/cashReceipt.py
class Receipt:
"""Класс для создания чека и наполнения его данными о покупках"""
__instance = None
__receipt_id = 0
def __new__(cls, *args, **kwargs):
cls.__instance = super().__new__(cls)
cls.__receipt_id += 1
return cls.__instance
def __init__(self, name, surname, patronymic, date, time, total):
self.name = name
self.surname = surname
self.patronymic = patronymic
self.date = date
self.time = time
self.total = total
self.items = []
@classmethod
def get_receipt_id(cls):
return cls.__receipt_id
@property
def id(self):
return self.get_receipt_id()
def add_item(self, item):
self.items.append(item)
def print_receipt(self):
print("Чек №", self.id)
print("Покупатель:", self.name, self.surname, self.patronymic)
print("Дата:", self.date)
print("Время:", self.time)
print("Итого:", self.total)
for item in self.items:
print(item.name, item.price, item.quantity)
class Item:
"""Класс для создания товара, его цены за штуку или 100г и количество"""
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
if __name__ == "__main__":
receipt = Receipt("Иван", "Иванов", "Иванович", "12.12.2020", "12:00", 100)
receipt.add_item(Item("Молоко", 50, 2))
receipt.add_item(Item("Хлеб", 30, 1))
receipt.print_receipt()
receipt2 = Receipt("Петр", "Петров", "Петрович", "12.12.2020", "12:00", 100)
receipt2.add_item(Item("Молоко", 50, 2))
receipt2.add_item(Item("Хлеб", 30, 1))
receipt2.print_receipt()
Первый запуск Pytest
Есть несколько способов запустить программу на Python. Один из них:
pytest cachReceipt/cachReceipt.py
Магия в том, что при таком запуске интерпретация кода меняется, и результат запуска тоже меняется:
============================== test session starts ==============================
platform win32 -- Python 3.12.0, pytest-8.3.4, pluggy-1.5.0
rootdir: C:\Users\Всеволод\IdeaProjects\pyTest
collected 0 items
============================= no tests ran in 0.01s =============================
Тесты не найдены.
Второй запуск Pytest
Для второго запуска нам нужно создать файл, в котором мы реализуем проверку по всем правилам pytest. Для этого нужно импортировать cashReceipt.py в файл с проверкой. В этом примере я использовал универсальный способ импорта, который не зависит от IDE и зависит только от ОС (различия только в том, как будут прописаны пути).
# myProject/tests/testCashReceipt.py
import pytest
import sys
sys.path.append("/home/user/IdeaProjects/myProject/cashReceipt")
from cashReceipt.cashReceipt import Receipt, Item
receipt = Receipt("Иван", "Иванов", "Иванович", "12.12.2020", "12:00", 100)
receipt.add_item(Item("Молоко", 50, 2))
receipt.add_item(Item("Хлеб", 30, 1))
items = receipt.items
@pytest.mark.parametrize("item", receipt.items)
def test_cashReceipt(item):
assert item.price <= 100
Чтобы такой импорт заработал, нужно настроить файл _ _init_ _.py, который делает из обычного каталога python-пакет.
# myProject/cashReceipt/__init__.py
from cashReceipt import *
Теперь становится понятно, как можно тестировать объекты с разными параметрами на входе. Мы можем протестировать (покрыть) все атрибуты и методы класса, который создаёт чеки. А если мы будем использовать несколько классов (например, класс «кошелёк»), то сможем делать интеграционные проверки (проверять взаимодействия разных классов между собой).
Конструкция @pytest.mark.parametrize освоена. Более подробно о ней в интернете много доступной информации.
Результат запуска:
============================= test session starts =============================
collecting ... collected 1 item
testCashReceipt.py::test_cash_receipt[receipt0] PASSED [100%]
============================== 1 passed in 0.01s ==============================
Но код в листинге выше не в стиле pytest, ему не хватает фикстур.
Третий запуск Pytest
Давайте создадим рядом с файлом тестирования файл conftest.py. Этот файл будет хранить объекты, которые мы тестируем.
# myProject/tests/conftest.py
import pytest
import sys
sys.path.append("/home/user/IdeaProjects/myProject/cashReceipt")
from cashReceipt.cashReceipt import Receipt, Item
@pytest.fixture
def receipt(request):
return Receipt(*request.param)
@pytest.fixture
def item(request):
return Item(*request.param)
И уберём лишний код из testCashReceipt
# myProject/tests/testCashReceipt.py
import pytest
@pytest.mark.parametrize("receipt", [("Иван", "Иванов", "Иванович", "12.12.2020", "12:00", 100)], indirect=True)
def test_cash_receipt(receipt):
assert receipt is not None
@pytest.mark.parametrize("item", [("Молоко", 50, 2), ("Хлеб", 30, 1)], indirect=True)
def test_cash_item(item):
assert item is not None
Не буду останавливаться на фикстурах, потому что в интернете и так полно о них информации.
А вот и результат нашего третьего запуска:
============================= test session starts =============================
collecting ... collected 3 items
testCashReceipt.py::test_cash_receipt[receipt0] PASSED [ 33%]
testCashReceipt.py::test_cash_item[item0] PASSED [ 66%]
testCashReceipt.py::test_cash_item[item1] PASSED [100%]
============================== 3 passed in 0.01s ==============================
Итог
В результате, всего за три запуска мы поняли, как сделать проверки на pytest, сохранить принципы ООП и всё структурировать. Класс!
Бонус
Структура проекта:
myProject
|-- cashReceipt
| |-- __init__.py
| |-- cashReceipt.py
|-- tests
|-- __init__.py
|-- conftest.py
|-- testCashReceipt.py
Комментарии (8)
edta_ff
26.01.2025 03:41Pytest — это первое, с чем сталкивается любой тестировщик, который хочет начать автоматизировать и развиваться в этой области.
Очень спорное утверждение. Столкнулся с Pytest уже после автоматизации на Ruby, Java, C#. Правильнее было бы написать, что это было первое , с чем вы столкнулись.
Магия в том, что при таком запуске интерпретация кода меняется, и результат запуска тоже меняется. Тесты не найдены.
Очень плохая идея объяснять чего-то новичку с помощью магии. Тем более на таком неочевидном примере. Можно взять самый просто тест без кучи классов, свойств и сразу показать как его запустить. Начинать же пример с того, как тесты не запустятся - сомнительная идея.
Добавление init.py в каждую директорию плохая практика.
Теперь становится понятно, как можно тестировать объекты с разными параметрами на входе.
Нет. Не становится.
В результате, всего за три запуска мы поняли, как сделать проверки на pytest, сохранить принципы ООП и всё структурировать. Класс!
Мы не поняли.
mrmcmva Автор
26.01.2025 03:41Этот туториал для самостоятельных людей, которые не ищут готовых решений, а хотят разобраться. Поэтому и не стал разжевывать вещи, которые не относятся к сути содержимого.
Если последовательно выполнить всё, что написано, то должно прийти понимание, если понимание не приходит, то можно задать конкретный вопрос в комментарии или разобраться самому.
Но спасибо за критику, она тоже полезна, как для авторов, так и для читателей.
rexer
Мне кажется, вариант с абсолютным путем зашитым в коде практически никогда не должен использоваться: в разработке многие используют разные ОС, плюс при переносе в CI/CD надо будет менять снова код.
mrmcmva Автор
Согласен, но в контексте данной статьи это не важно. Можно вынести путь в проперти, или прописать относительные пути, но это никак не меняет суть изложенного материала.
rexer
Не соглашусь, так как вы же показываете новичку это и явно то, что максимально не будет применяться и по сути является ошибочным показывать не стоит - чтобы не смущать. Лучше показать правильные и используемые варианты (кмк).
mrmcmva Автор
Согласен с вами, учту на будущее. Мастерством нужно прирастать и в этом компоненте. Спасибо за замечания!