diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4c2baa391 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +игнорирем все файлы с расширением +.env +.coverage +*.pyc +*.bak + +игнорируем файлы +Thumbs.db +Desktop.ini + +игнорируем директории +.pytest_cache/ +venv/ +__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md index 272081708..413ba5590 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,37 @@ ### Реализованные сценарии -Созданы юнит-тесты, покрывающие классы `Bun`, `Burger`, `Ingredient`, `Database` +Создан юнит-тест, покрывающий класс `Burger` + +Использованы **моки** и **параметризация** для изоляции зависимостей и проверки различных сценариев. + +**Проверяемые методы:** +- Инициализация бургера (`__init__`) +- Установка булочки (`set_buns`) +- Добавление ингредиентов (`add_ingredient`) +- Удаление ингредиентов по индексу (`remove_ingredient`) +- Перемещение ингредиентов (`move_ingredient`) — параметризованный тест (6 сценариев) +- Расчёт стоимости бургера (`get_price`) — параметризованный тест (8 сценариев) +- Формирование чека (`get_receipt`) — 5 различных сценариев + Процент покрытия 100% (отчет: `htmlcov/index.html`) ### Структура проекта -- `praktikum` - пакет, содержащий код программы -- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. +## 📂 Структура проекта +Diplom_1/ +├── praktikum/ # Код программы (исходники) +│ ├── init.py +│ ├── bun.py +│ ├── ingredient.py +│ └── burger.py +├── tests/ # Тесты +│ └── test_burger.py # Тесты для класса Burger +├── requirements.txt # Зависимости +├── pytest.ini # Настройки pytest +├── .gitignore # Игнорируемые файлы +└── README.md # Описание проекта ### Запуск автотестов 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/praktikum.py b/praktikum.py index ec522fa6d..ea60c1778 100644 --- a/praktikum.py +++ b/praktikum.py @@ -39,3 +39,5 @@ def main(): if __name__ == "__main__": main() + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..392c73154 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest==7.4.3 +pytest-cov==4.1.0 \ No newline at end of file diff --git a/__init__.py b/tests/__init__.py similarity index 100% rename from __init__.py rename to tests/__init__.py diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..853881140 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,333 @@ +import pytest +from unittest.mock import Mock +from burger import Burger + + +class TestBurger: + #Тесты для класса Burger + def test_init(self): + """Тест 1: Проверка инициализации бургера""" + burger = Burger() + assert burger.bun is None, "Булочка должна быть None при инициализации" + assert burger.ingredients == [], "Список ингредиентов должен быть пустым" + + def test_set_buns(self): + """Тест 2: Установка булочки""" + mock_bun = Mock() + mock_bun.get_name.return_value = "Test Bun" + mock_bun.get_price.return_value = 50.0 + + burger = Burger() + burger.set_buns(mock_bun) + + assert burger.bun == mock_bun, "Булочка должна сохраниться в объекте" + assert burger.bun.get_name() == "Test Bun", "Название булочки должно совпадать" + assert burger.bun.get_price() == 50.0, "Цена булочки должна совпадать" + + def test_add_ingredient(self): + """Тест 3: Добавление одного ингредиента""" + mock_ingredient = Mock() + mock_ingredient.get_name.return_value = "Test Ingredient" + mock_ingredient.get_price.return_value = 30.0 + mock_ingredient.get_type.return_value = "sauce" + + burger = Burger() + burger.add_ingredient(mock_ingredient) + + assert len(burger.ingredients) == 1, "Должен быть 1 ингредиент" + assert burger.ingredients[0] == mock_ingredient, "Ингредиент должен сохраниться" + assert burger.ingredients[0].get_name() == "Test Ingredient" + + def test_add_multiple_ingredients(self): + """Тест 4: Добавление нескольких ингредиентов""" + mock_ing1 = Mock() + mock_ing2 = Mock() + mock_ing3 = Mock() + + burger = Burger() + burger.add_ingredient(mock_ing1) + burger.add_ingredient(mock_ing2) + burger.add_ingredient(mock_ing3) + + assert len(burger.ingredients) == 3, "Должно быть 3 ингредиента" + assert burger.ingredients == [mock_ing1, mock_ing2, mock_ing3], "Порядок должен сохраняться" + + def test_remove_ingredient(self): + """Тест 5: Удаление ингредиента по индексу""" + mock_ing1 = Mock() + mock_ing2 = Mock() + mock_ing3 = Mock() + + burger = Burger() + burger.add_ingredient(mock_ing1) + burger.add_ingredient(mock_ing2) + burger.add_ingredient(mock_ing3) + + burger.remove_ingredient(1) # Удаляем второй ингредиент + + assert len(burger.ingredients) == 2, "Должно остаться 2 ингредиента" + assert burger.ingredients == [mock_ing1, mock_ing3], "Порядок должен сохраниться" + + def test_remove_ingredient_first(self): + """Тест 6: Удаление первого ингредиента""" + mock_ing1 = Mock() + mock_ing2 = Mock() + + burger = Burger() + burger.add_ingredient(mock_ing1) + burger.add_ingredient(mock_ing2) + + burger.remove_ingredient(0) + + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == mock_ing2 + + def test_remove_ingredient_last(self): + """Тест 7: Удаление последнего ингредиента""" + mock_ing1 = Mock() + mock_ing2 = Mock() + + burger = Burger() + burger.add_ingredient(mock_ing1) + burger.add_ingredient(mock_ing2) + + burger.remove_ingredient(1) + + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == mock_ing1 + + @pytest.mark.parametrize("from_idx,to_idx,expected_order", [ + # Перемещение первого на второе место + (0, 1, [1, 0, 2]), + # Перемещение второго на первое место + (1, 0, [1, 0, 2]), + # Перемещение первого в конец + (0, 2, [1, 2, 0]), + # Перемещение последнего на первое место + (2, 0, [2, 0, 1]), + # Перемещение в ту же позицию (ничего не меняется) + (0, 0, [0, 1, 2]), + (1, 1, [0, 1, 2]), + ]) + + def test_move_ingredient(self, from_idx, to_idx, expected_order): + """Тест 8: Перемещение ингредиента с параметризацией""" + # Создаем 3 разных мока + mock_ing1 = Mock() + mock_ing2 = Mock() + mock_ing3 = Mock() + ingredients = [mock_ing1, mock_ing2, mock_ing3] + + burger = Burger() + for ing in ingredients: + burger.add_ingredient(ing) + + burger.move_ingredient(from_idx, to_idx) + + # Проверяем порядок + expected_ingredients = [ingredients[i] for i in expected_order] + assert burger.ingredients == expected_ingredients, f"Неверный порядок при перемещении с {from_idx} на {to_idx}" + + @pytest.mark.parametrize("bun_price,ingredient_prices,expected_price", [ + # Только булочка + (100.0, [], 200.0), + # Булочка + один ингредиент + (100.0, [50.0], 250.0), + # Булочка + два ингредиента + (100.0, [50.0, 30.0], 280.0), + # Дешевая булочка + (10.0, [5.0, 3.0], 28.0), + # Бесплатная булочка + (0.0, [50.0], 50.0), + # Бесплатные ингредиенты + (100.0, [0.0, 0.0], 200.0), + # Только бесплатная булочка + (0.0, [], 0.0), + # Много ингредиентов + (50.0, [10.0, 20.0, 30.0, 40.0], 200.0), + ]) + + def test_get_price(self, bun_price, ingredient_prices, expected_price): + """Тест 9: Расчет цены бургера с параметризацией""" + # Создаем мок для булочки + mock_bun = Mock() + mock_bun.get_price.return_value = bun_price + + burger = Burger() + burger.set_buns(mock_bun) + + # Добавляем ингредиенты с заданными ценами + for price in ingredient_prices: + mock_ing = Mock() + mock_ing.get_price.return_value = price + burger.add_ingredient(mock_ing) + + assert burger.get_price() == expected_price, \ + f"Цена {burger.get_price()} не совпадает с ожидаемой {expected_price}" + + def test_get_receipt_without_ingredients(self): + """Тест 10: Чек без ингредиентов (только булочка)""" + mock_bun = Mock() + mock_bun.get_name.return_value = "Black Bun" + mock_bun.get_price.return_value = 100.0 + + burger = Burger() + burger.set_buns(mock_bun) + + expected_receipt = ( + "(==== Black Bun ====)\n" + "(==== Black Bun ====)\n" + "\n" + "Price: 200.0" + ) + + assert burger.get_receipt() == expected_receipt + + def test_get_receipt_with_ingredients(self): + """Тест 11: Чек с ингредиентами""" + mock_bun = Mock() + mock_bun.get_name.return_value = "White Bun" + mock_bun.get_price.return_value = 80.0 + + mock_ing1 = Mock() + mock_ing1.get_type.return_value = "sauce" + mock_ing1.get_name.return_value = "Spicy Sauce" + mock_ing1.get_price.return_value = 40.0 + + mock_ing2 = Mock() + mock_ing2.get_type.return_value = "filling" + mock_ing2.get_name.return_value = "Chicken Cutlet" + mock_ing2.get_price.return_value = 120.0 + + burger = Burger() + burger.set_buns(mock_bun) + burger.add_ingredient(mock_ing1) + burger.add_ingredient(mock_ing2) + + expected_receipt = ( + "(==== White Bun ====)\n" + "= sauce Spicy Sauce =\n" + "= filling Chicken Cutlet =\n" + "(==== White Bun ====)\n" + "\n" + "Price: 320.0" + ) + + assert burger.get_receipt() == expected_receipt + + def test_get_receipt_with_one_ingredient(self): + """Тест 12: Чек с одним ингредиентом""" + mock_bun = Mock() + mock_bun.get_name.return_value = "Rye Bun" + mock_bun.get_price.return_value = 60.0 + + mock_ing = Mock() + mock_ing.get_type.return_value = "sauce" + mock_ing.get_name.return_value = "BBQ Sauce" + mock_ing.get_price.return_value = 30.0 + + burger = Burger() + burger.set_buns(mock_bun) + burger.add_ingredient(mock_ing) + + expected_receipt = ( + "(==== Rye Bun ====)\n" + "= sauce BBQ Sauce =\n" + "(==== Rye Bun ====)\n" + "\n" + "Price: 150.0" + ) + + assert burger.get_receipt() == expected_receipt + + def test_get_receipt_with_mixed_case_types(self): + """Тест 13: Чек с разным регистром типов ингредиентов""" + mock_bun = Mock() + mock_bun.get_name.return_value = "Bun" + mock_bun.get_price.return_value = 50.0 + + mock_ing1 = Mock() + mock_ing1.get_type.return_value = "SAUCE" + mock_ing1.get_name.return_value = "Ketchup" + mock_ing1.get_price.return_value = 20.0 + + mock_ing2 = Mock() + mock_ing2.get_type.return_value = "FILLING" + mock_ing2.get_name.return_value = "Cheese" + mock_ing2.get_price.return_value = 30.0 + + burger = Burger() + burger.set_buns(mock_bun) + burger.add_ingredient(mock_ing1) + burger.add_ingredient(mock_ing2) + + expected_receipt = ( + "(==== Bun ====)\n" + "= sauce Ketchup =\n" + "= filling Cheese =\n" + "(==== Bun ====)\n" + "\n" + "Price: 150.0" + ) + + assert burger.get_receipt() == expected_receipt + + def test_get_receipt_complex_scenario(self): + """Тест 14: Сложный сценарий со многими ингредиентами""" + mock_bun = Mock() + mock_bun.get_name.return_value = "Sesame Bun" + mock_bun.get_price.return_value = 90.0 + + ingredients_data = [ + ("sauce", "Mayo", 25.0), + ("filling", "Beef Patty", 150.0), + ("sauce", "Mustard", 15.0), + ("filling", "Bacon", 80.0), + ("sauce", "Garlic Sauce", 20.0), + ] + + burger = Burger() + burger.set_buns(mock_bun) + + for ing_type, ing_name, ing_price in ingredients_data: + mock_ing = Mock() + mock_ing.get_type.return_value = ing_type + mock_ing.get_name.return_value = ing_name + mock_ing.get_price.return_value = ing_price + burger.add_ingredient(mock_ing) + + expected_receipt = ( + "(==== Sesame Bun ====)\n" + "= sauce Mayo =\n" + "= filling Beef Patty =\n" + "= sauce Mustard =\n" + "= filling Bacon =\n" + "= sauce Garlic Sauce =\n" + "(==== Sesame Bun ====)\n" + "\n" + "Price: 470.0" + ) + + assert burger.get_receipt() == expected_receipt + + def test_get_receipt_with_mock_price_calculation(self): + """Тест 15: Проверка, что в чеке используется метод get_price()""" + mock_bun = Mock() + mock_bun.get_name.return_value = "Test Bun" + mock_bun.get_price.return_value = 70.0 + + mock_ing = Mock() + mock_ing.get_type.return_value = "sauce" + mock_ing.get_name.return_value = "Test Sauce" + mock_ing.get_price.return_value = 30.0 + + burger = Burger() + burger.set_buns(mock_bun) + burger.add_ingredient(mock_ing) + + receipt = burger.get_receipt() + + # Проверяем, что цена вычислена правильно + assert "Price: 170.0" in receipt # 70*2 + 30 = 170 + + \ No newline at end of file