diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..67aa77309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +.pytest_cache/ +.coverage +htmlcov/ +*.pyc diff --git a/README.md b/README.md index 272081708..4fb89dd02 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,35 @@ ### Реализованные сценарии -Созданы юнит-тесты, покрывающие классы `Bun`, `Burger`, `Ingredient`, `Database` +Класс `Burger` покрыт unit-тестами с использованием моков и параметризации: -Процент покрытия 100% (отчет: `htmlcov/index.html`) +- создание бургера; +- установка булочки; +- добавление и удаление ингредиента; +- перемещение ингредиента; +- расчёт цены; +- формирование чека. + +Покрытие файла `burger.py` — 100%. ### Структура проекта -- `praktikum` - пакет, содержащий код программы -- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. +- `burger.py` — тестируемый класс `Burger`; +- `bun.py`, `ingredient.py` — модели, используемые классом `Burger`; +- `tests/test_burger.py` — unit-тесты; +- `requirements.txt` — зависимости проекта; +- `pytest.ini` — настройки pytest. ### Запуск автотестов **Установка зависимостей** -> `$ pip install -r requirements.txt` +> `$ python -m pip install -r requirements.txt` + +**Запуск автотестов с проверкой обязательного покрытия 100%** + +> `$ pytest tests --cov=burger --cov-report=term-missing --cov-fail-under=100` -**Запуск автотестов и создание HTML-отчета о покрытии** +**Создание HTML-отчёта** -> `$ pytest --cov=praktikum --cov-report=html` +> `$ pytest tests --cov=burger --cov-report=html --cov-fail-under=100` diff --git a/burger.py b/burger.py index 2b3b6a88b..3f71aaa29 100644 --- a/burger.py +++ b/burger.py @@ -1,7 +1,7 @@ from typing import List -from praktikum.bun import Bun -from praktikum.ingredient import Ingredient +from bun import Bun +from ingredient import Ingredient class Burger: diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..b9c31b2b5 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -ra diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..cb34cb8ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest==9.1.1 +pytest-cov==7.1.0 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..dc36e96e2 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,94 @@ +from unittest.mock import Mock + +import pytest + +from bun import Bun +from burger import Burger +from ingredient import Ingredient + + +@pytest.fixture +def burger(): + return Burger() + + +class TestBurger: + def test_init_creates_burger_without_bun_and_ingredients(self): + burger = Burger() + + assert burger.bun is None and burger.ingredients == [] + + def test_set_buns_sets_bun(self, burger): + bun = Mock(spec=Bun) + + burger.set_buns(bun) + + assert burger.bun is bun + + def test_add_ingredient_appends_ingredient(self, burger): + ingredient = Mock(spec=Ingredient) + + burger.add_ingredient(ingredient) + + assert burger.ingredients == [ingredient] + + @pytest.mark.parametrize("index, expected_indexes", + [(0, [1, 2]),(1, [0, 2]),(2, [0, 1]),],) + def test_remove_ingredient_removes_item_by_index( + self, burger, index, expected_indexes): + ingredients = [Mock(spec=Ingredient) for _ in range(3)] + burger.ingredients = ingredients.copy() + + burger.remove_ingredient(index) + + assert burger.ingredients == [ingredients[i] for i in expected_indexes] + + @pytest.mark.parametrize("index, new_index, expected_indexes", + [(0, 2, [1, 2, 0]),(2, 0, [2, 0, 1]),(1, 1, [0, 1, 2]),],) + def test_move_ingredient_moves_item_to_new_index( + self, burger, index, new_index, expected_indexes): + ingredients = [Mock(spec=Ingredient) for _ in range(3)] + burger.ingredients = ingredients.copy() + + burger.move_ingredient(index, new_index) + + assert burger.ingredients == [ingredients[i] for i in expected_indexes] + + @pytest.mark.parametrize("bun_price, ingredient_prices, expected_price", + [(100, [], 200),(125.5, [10, 20.5], 281.5),],) + def test_get_price_returns_price_of_two_buns_and_all_ingredients( + self, burger, bun_price, ingredient_prices, expected_price): + bun = Mock(spec=Bun) + bun.get_price.return_value = bun_price + ingredients = [Mock(spec=Ingredient) for _ in ingredient_prices] + for ingredient, price in zip(ingredients, ingredient_prices): + ingredient.get_price.return_value = price + burger.set_buns(bun) + burger.ingredients = ingredients + + result = burger.get_price() + + assert result == expected_price + + @pytest.mark.parametrize( + "ingredient_type, expected_type", + [("SAUCE", "sauce"),("FILLING", "filling"),],) + def test_get_receipt_returns_formatted_receipt( + self, burger, ingredient_type, expected_type): + bun = Mock(spec=Bun) + bun.get_name.return_value = "space bun" + bun.get_price.return_value = 100 + ingredient = Mock(spec=Ingredient) + ingredient.get_type.return_value = ingredient_type + ingredient.get_name.return_value = "meteor" + ingredient.get_price.return_value = 25 + burger.set_buns(bun) + burger.add_ingredient(ingredient) + + result = burger.get_receipt() + + assert result == ( + "(==== space bun ====)\n" + f"= {expected_type} meteor =\n" + "(==== space bun ====)\n\n" + "Price: 225")