Привет, друзья!
Сегодня я хочу рассказать о 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
