From 50ef87399231daf66762580aab49d96efd0ef94f Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Sat, 4 Jul 2026 20:03:51 +0300 Subject: [PATCH 01/13] Create test_burger.py --- test_burger.py | 182 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 test_burger.py diff --git a/test_burger.py b/test_burger.py new file mode 100644 index 000000000..d3fc9f682 --- /dev/null +++ b/test_burger.py @@ -0,0 +1,182 @@ +# tests/test_burger.py + +import pytest +from unittest.mock import Mock, patch +from praktikum.burger import Burger +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +@pytest.fixture +def burger(): + """Фикстура для создания чистого бургера перед каждым тестом.""" + return Burger() + + +# ---- Тесты для set_buns ---- + +def test_set_buns(burger): + mock_bun = Mock(spec=Bun) + mock_bun.get_name.return_value = "white bun" + mock_bun.get_price.return_value = 100.0 + + burger.set_buns(mock_bun) + + assert burger.bun == mock_bun + assert burger.bun.get_name() == "white bun" + assert burger.bun.get_price() == 100.0 + + +# ---- Тесты для add_ingredient ---- + +def test_add_ingredient(burger): + mock_ingredient = Mock(spec=Ingredient) + mock_ingredient.get_name.return_value = "cutlet" + mock_ingredient.get_price.return_value = 150.0 + + burger.add_ingredient(mock_ingredient) + + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == mock_ingredient + assert burger.ingredients[0].get_name() == "cutlet" + + +# ---- Тесты для remove_ingredient ---- + +def test_remove_ingredient(burger): + # Добавляем два ингредиента + ing1 = Mock(spec=Ingredient) + ing2 = Mock(spec=Ingredient) + burger.add_ingredient(ing1) + burger.add_ingredient(ing2) + + burger.remove_ingredient(0) + + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == ing2 + + +def test_remove_ingredient_out_of_range(burger): + # Если индекс не существует, должен быть IndexError + with pytest.raises(IndexError): + burger.remove_ingredient(0) # список пуст + + +# ---- Тесты для move_ingredient ---- + +def test_move_ingredient(burger): + # Добавляем три ингредиента + ing1 = Mock(spec=Ingredient) + ing2 = Mock(spec=Ingredient) + ing3 = Mock(spec=Ingredient) + burger.add_ingredient(ing1) + burger.add_ingredient(ing2) + burger.add_ingredient(ing3) + + # Перемещаем ингредиент с индекса 0 на индекс 2 + burger.move_ingredient(0, 2) + + # Ожидаемый порядок: ing2, ing3, ing1 + assert burger.ingredients[0] == ing2 + assert burger.ingredients[1] == ing3 + assert burger.ingredients[2] == ing1 + + +def test_move_ingredient_out_of_range(burger): + # При перемещении с несуществующим индексом также будет ошибка + with pytest.raises(IndexError): + burger.move_ingredient(5, 0) + + +# ---- Тесты для get_price (с параметризацией) ---- + +@pytest.mark.parametrize( + "bun_price, ingredient_prices, expected_price", + [ + (100.0, [50.0, 30.0], 280.0), # 2*100 + 50 + 30 = 280 + (200.0, [], 400.0), # только булочка (2*200) + (150.0, [10.0, 20.0, 30.0], 360.0), # 2*150 + 10+20+30 = 360 + ] +) +def test_get_price(burger, bun_price, ingredient_prices, expected_price): + # Мокаем булочку + mock_bun = Mock(spec=Bun) + mock_bun.get_price.return_value = bun_price + burger.set_buns(mock_bun) + + # Добавляем моки ингредиентов с заданными ценами + for price in ingredient_prices: + mock_ing = Mock(spec=Ingredient) + mock_ing.get_price.return_value = price + burger.add_ingredient(mock_ing) + + actual_price = burger.get_price() + assert actual_price == expected_price + + +# ---- Тесты для get_receipt (с параметризацией) ---- + +@pytest.mark.parametrize( + "bun_name, ingredients_info, expected_lines", + [ + # Случай без ингредиентов + ( + "black bun", + [], + [ + "(==== black bun ====)", + "(==== black bun ====)", + "Price: 200.0" # цена булочки 100 * 2 = 200 + ] + ), + # С одним ингредиентом (соус) + ( + "white bun", + [("SAUCE", "hot sauce", 100.0)], + [ + "(==== white bun ====)", + "= sauce hot sauce =", + "(==== white bun ====)", + "Price: 500.0" # 2*200 + 100 = 500 + ] + ), + # С двумя ингредиентами (соус и начинка) + ( + "red bun", + [ + ("SAUCE", "chili sauce", 50.0), + ("FILLING", "cutlet", 150.0) + ], + [ + "(==== red bun ====)", + "= sauce chili sauce =", + "= filling cutlet =", + "(==== red bun ====)", + "Price: 800.0" # 2*300 + 50 + 150 = 800 + ] + ), + ] +) +def test_get_receipt(burger, bun_name, ingredients_info, expected_lines): + # Мокаем булочку + mock_bun = Mock(spec=Bun) + mock_bun.get_name.return_value = bun_name + mock_bun.get_price.return_value = 100.0 if bun_name == "black bun" else 200.0 if bun_name == "white bun" else 300.0 + burger.set_buns(mock_bun) + + # Добавляем ингредиенты + for ing_type, ing_name, ing_price in ingredients_info: + mock_ing = Mock(spec=Ingredient) + 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) + + receipt = burger.get_receipt() + receipt_lines = receipt.split('\n') + + # Проверяем, что все ожидаемые строки присутствуют в чеке (и в правильном порядке) + assert len(receipt_lines) == len(expected_lines), "Количество строк не совпадает" + for expected, actual in zip(expected_lines, receipt_lines): + assert expected == actual, f"Ожидалось: '{expected}', получено: '{actual}'" From 16953af8df1ab7e5284162ee28510e3cb1f5daa7 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Sat, 4 Jul 2026 20:17:34 +0300 Subject: [PATCH 02/13] Update test_burger.py --- test_burger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_burger.py b/test_burger.py index d3fc9f682..629c771c5 100644 --- a/test_burger.py +++ b/test_burger.py @@ -179,4 +179,4 @@ def test_get_receipt(burger, bun_name, ingredients_info, expected_lines): # Проверяем, что все ожидаемые строки присутствуют в чеке (и в правильном порядке) assert len(receipt_lines) == len(expected_lines), "Количество строк не совпадает" for expected, actual in zip(expected_lines, receipt_lines): - assert expected == actual, f"Ожидалось: '{expected}', получено: '{actual}'" + assert expected == actual, f"Ожидалось: '{expected}', получено: '{actual}'" From 830e5705cb12a540a6134d4c5b15652e331f6fc7 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:31:37 +0300 Subject: [PATCH 03/13] Delete README.md --- README.md | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 272081708..000000000 --- a/README.md +++ /dev/null @@ -1,24 +0,0 @@ -## Задание 1: Юнит-тесты - -### Автотесты для проверки программы, которая помогает заказать бургер в Stellar Burgers - -### Реализованные сценарии - -Созданы юнит-тесты, покрывающие классы `Bun`, `Burger`, `Ingredient`, `Database` - -Процент покрытия 100% (отчет: `htmlcov/index.html`) - -### Структура проекта - -- `praktikum` - пакет, содержащий код программы -- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. - -### Запуск автотестов - -**Установка зависимостей** - -> `$ pip install -r requirements.txt` - -**Запуск автотестов и создание HTML-отчета о покрытии** - -> `$ pytest --cov=praktikum --cov-report=html` From 39d0ae644faeeb1ee459d7688c672e1561975fa7 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:32:08 +0300 Subject: [PATCH 04/13] Delete __init__.py --- __init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 0f22bece1c3020bc4b47d7e3e815ff02627f3f64 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:32:22 +0300 Subject: [PATCH 05/13] Delete bun.py --- bun.py | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 bun.py diff --git a/bun.py b/bun.py deleted file mode 100644 index 5504bc1f4..000000000 --- a/bun.py +++ /dev/null @@ -1,15 +0,0 @@ -class Bun: - """ - Модель булочки для бургера. - Булочке можно дать название и назначить цену. - """ - - def __init__(self, name: str, price: float): - self.name = name - self.price = price - - def get_name(self) -> str: - return self.name - - def get_price(self) -> float: - return self.price From 928c60a7382641d9b6e982acbd201661efd7eb74 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:32:49 +0300 Subject: [PATCH 06/13] Delete burger.py --- burger.py | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 burger.py diff --git a/burger.py b/burger.py deleted file mode 100644 index 2b3b6a88b..000000000 --- a/burger.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import List - -from praktikum.bun import Bun -from praktikum.ingredient import Ingredient - - -class Burger: - """ - Модель бургера. - Бургер состоит из булочек и ингредиентов (начинка или соус). - Ингредиенты можно перемещать и удалять. - Можно распечать чек с информацией о бургере. - """ - - def __init__(self): - self.bun = None - self.ingredients: List[Ingredient] = [] - - def set_buns(self, bun: Bun): - self.bun = bun - - def add_ingredient(self, ingredient: Ingredient): - self.ingredients.append(ingredient) - - def remove_ingredient(self, index: int): - del self.ingredients[index] - - def move_ingredient(self, index: int, new_index: int): - self.ingredients.insert(new_index, self.ingredients.pop(index)) - - def get_price(self) -> float: - price = self.bun.get_price() * 2 - - for ingredient in self.ingredients: - price += ingredient.get_price() - - return price - - def get_receipt(self) -> str: - receipt: List[str] = [f'(==== {self.bun.get_name()} ====)'] - - for ingredient in self.ingredients: - receipt.append(f'= {str(ingredient.get_type()).lower()} {ingredient.get_name()} =') - - receipt.append(f'(==== {self.bun.get_name()} ====)\n') - receipt.append(f'Price: {self.get_price()}') - - return '\n'.join(receipt) From 7eeaf77b9c83a7ec30b573527e4369adffa3377e Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:33:02 +0300 Subject: [PATCH 07/13] Delete database.py --- database.py | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 database.py diff --git a/database.py b/database.py deleted file mode 100644 index 4c75baf71..000000000 --- a/database.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import List - -from praktikum.bun import Bun -from praktikum.ingredient import Ingredient -from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING - - -class Database: - """ - Класс с методами по работе с базой данных. - """ - - def __init__(self): - self.buns: List[Bun] = [] - self.ingredients: List[Ingredient] = [] - - self.buns.append(Bun("black bun", 100)) - self.buns.append(Bun("white bun", 200)) - self.buns.append(Bun("red bun", 300)) - - self.ingredients.append(Ingredient(INGREDIENT_TYPE_SAUCE, "hot sauce", 100)) - self.ingredients.append(Ingredient(INGREDIENT_TYPE_SAUCE, "sour cream", 200)) - self.ingredients.append(Ingredient(INGREDIENT_TYPE_SAUCE, "chili sauce", 300)) - - self.ingredients.append(Ingredient(INGREDIENT_TYPE_FILLING, "cutlet", 100)) - self.ingredients.append(Ingredient(INGREDIENT_TYPE_FILLING, "dinosaur", 200)) - self.ingredients.append(Ingredient(INGREDIENT_TYPE_FILLING, "sausage", 300)) - - def available_buns(self) -> List[Bun]: - return self.buns - - def available_ingredients(self) -> List[Ingredient]: - return self.ingredients From 90c2b6b5c926957bb06cf0c8e28ded2799fbbf83 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:33:19 +0300 Subject: [PATCH 08/13] Delete ingredient.py --- ingredient.py | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 ingredient.py diff --git a/ingredient.py b/ingredient.py deleted file mode 100644 index 0e50db8a2..000000000 --- a/ingredient.py +++ /dev/null @@ -1,20 +0,0 @@ -class Ingredient: - """ - Модель ингредиента. - Ингредиент: начинка или соус. - У ингредиента есть тип (начинка или соус), название и цена. - """ - - def __init__(self, ingredient_type: str, name: str, price: float): - self.type = ingredient_type - self.name = name - self.price = price - - def get_price(self) -> float: - return self.price - - def get_name(self) -> str: - return self.name - - def get_type(self) -> str: - return self.type From 153bd103f93105332caf1ce597a99d399262da1e Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:33:32 +0300 Subject: [PATCH 09/13] Delete ingredient_types.py --- ingredient_types.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 ingredient_types.py diff --git a/ingredient_types.py b/ingredient_types.py deleted file mode 100644 index 34940ad5d..000000000 --- a/ingredient_types.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Перечисление с типами ингредиентов. -SAUCE – соус -FILLING – начинка -""" -INGREDIENT_TYPE_SAUCE = 'SAUCE' -INGREDIENT_TYPE_FILLING = 'FILLING' From 9b2e96c0b5d4f754b771d500b9bc9bd540bffe6c Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:33:44 +0300 Subject: [PATCH 10/13] Delete praktikum.py --- praktikum.py | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 praktikum.py diff --git a/praktikum.py b/praktikum.py deleted file mode 100644 index ec522fa6d..000000000 --- a/praktikum.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import List - -from praktikum.bun import Bun -from praktikum.burger import Burger -from praktikum.database import Database -from praktikum.ingredient import Ingredient - - -def main(): - # Инициализируем базу данных - database: Database = Database() - - # Создадим новый бургер - burger: Burger = Burger() - - # Считаем список доступных булок из базы данных - buns: List[Bun] = database.available_buns() - - # Считаем список доступных ингредиентов из базы данных - ingredients: List[Ingredient] = database.available_ingredients() - - # Соберём бургер - burger.set_buns(buns[0]) - - burger.add_ingredient(ingredients[1]) - burger.add_ingredient(ingredients[4]) - burger.add_ingredient(ingredients[3]) - burger.add_ingredient(ingredients[5]) - - # Переместим слой с ингредиентом - burger.move_ingredient(2, 1) - - # Удалим ингредиент - burger.remove_ingredient(3) - - # Распечатаем рецепт бургера - print(burger.get_receipt()) - - -if __name__ == "__main__": - main() From 981f4d787cef3475a4f881ffdedb7422a6372e69 Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 18:33:56 +0300 Subject: [PATCH 11/13] Delete test_burger.py --- test_burger.py | 182 ------------------------------------------------- 1 file changed, 182 deletions(-) delete mode 100644 test_burger.py diff --git a/test_burger.py b/test_burger.py deleted file mode 100644 index 629c771c5..000000000 --- a/test_burger.py +++ /dev/null @@ -1,182 +0,0 @@ -# tests/test_burger.py - -import pytest -from unittest.mock import Mock, patch -from praktikum.burger import Burger -from praktikum.bun import Bun -from praktikum.ingredient import Ingredient -from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING - - -@pytest.fixture -def burger(): - """Фикстура для создания чистого бургера перед каждым тестом.""" - return Burger() - - -# ---- Тесты для set_buns ---- - -def test_set_buns(burger): - mock_bun = Mock(spec=Bun) - mock_bun.get_name.return_value = "white bun" - mock_bun.get_price.return_value = 100.0 - - burger.set_buns(mock_bun) - - assert burger.bun == mock_bun - assert burger.bun.get_name() == "white bun" - assert burger.bun.get_price() == 100.0 - - -# ---- Тесты для add_ingredient ---- - -def test_add_ingredient(burger): - mock_ingredient = Mock(spec=Ingredient) - mock_ingredient.get_name.return_value = "cutlet" - mock_ingredient.get_price.return_value = 150.0 - - burger.add_ingredient(mock_ingredient) - - assert len(burger.ingredients) == 1 - assert burger.ingredients[0] == mock_ingredient - assert burger.ingredients[0].get_name() == "cutlet" - - -# ---- Тесты для remove_ingredient ---- - -def test_remove_ingredient(burger): - # Добавляем два ингредиента - ing1 = Mock(spec=Ingredient) - ing2 = Mock(spec=Ingredient) - burger.add_ingredient(ing1) - burger.add_ingredient(ing2) - - burger.remove_ingredient(0) - - assert len(burger.ingredients) == 1 - assert burger.ingredients[0] == ing2 - - -def test_remove_ingredient_out_of_range(burger): - # Если индекс не существует, должен быть IndexError - with pytest.raises(IndexError): - burger.remove_ingredient(0) # список пуст - - -# ---- Тесты для move_ingredient ---- - -def test_move_ingredient(burger): - # Добавляем три ингредиента - ing1 = Mock(spec=Ingredient) - ing2 = Mock(spec=Ingredient) - ing3 = Mock(spec=Ingredient) - burger.add_ingredient(ing1) - burger.add_ingredient(ing2) - burger.add_ingredient(ing3) - - # Перемещаем ингредиент с индекса 0 на индекс 2 - burger.move_ingredient(0, 2) - - # Ожидаемый порядок: ing2, ing3, ing1 - assert burger.ingredients[0] == ing2 - assert burger.ingredients[1] == ing3 - assert burger.ingredients[2] == ing1 - - -def test_move_ingredient_out_of_range(burger): - # При перемещении с несуществующим индексом также будет ошибка - with pytest.raises(IndexError): - burger.move_ingredient(5, 0) - - -# ---- Тесты для get_price (с параметризацией) ---- - -@pytest.mark.parametrize( - "bun_price, ingredient_prices, expected_price", - [ - (100.0, [50.0, 30.0], 280.0), # 2*100 + 50 + 30 = 280 - (200.0, [], 400.0), # только булочка (2*200) - (150.0, [10.0, 20.0, 30.0], 360.0), # 2*150 + 10+20+30 = 360 - ] -) -def test_get_price(burger, bun_price, ingredient_prices, expected_price): - # Мокаем булочку - mock_bun = Mock(spec=Bun) - mock_bun.get_price.return_value = bun_price - burger.set_buns(mock_bun) - - # Добавляем моки ингредиентов с заданными ценами - for price in ingredient_prices: - mock_ing = Mock(spec=Ingredient) - mock_ing.get_price.return_value = price - burger.add_ingredient(mock_ing) - - actual_price = burger.get_price() - assert actual_price == expected_price - - -# ---- Тесты для get_receipt (с параметризацией) ---- - -@pytest.mark.parametrize( - "bun_name, ingredients_info, expected_lines", - [ - # Случай без ингредиентов - ( - "black bun", - [], - [ - "(==== black bun ====)", - "(==== black bun ====)", - "Price: 200.0" # цена булочки 100 * 2 = 200 - ] - ), - # С одним ингредиентом (соус) - ( - "white bun", - [("SAUCE", "hot sauce", 100.0)], - [ - "(==== white bun ====)", - "= sauce hot sauce =", - "(==== white bun ====)", - "Price: 500.0" # 2*200 + 100 = 500 - ] - ), - # С двумя ингредиентами (соус и начинка) - ( - "red bun", - [ - ("SAUCE", "chili sauce", 50.0), - ("FILLING", "cutlet", 150.0) - ], - [ - "(==== red bun ====)", - "= sauce chili sauce =", - "= filling cutlet =", - "(==== red bun ====)", - "Price: 800.0" # 2*300 + 50 + 150 = 800 - ] - ), - ] -) -def test_get_receipt(burger, bun_name, ingredients_info, expected_lines): - # Мокаем булочку - mock_bun = Mock(spec=Bun) - mock_bun.get_name.return_value = bun_name - mock_bun.get_price.return_value = 100.0 if bun_name == "black bun" else 200.0 if bun_name == "white bun" else 300.0 - burger.set_buns(mock_bun) - - # Добавляем ингредиенты - for ing_type, ing_name, ing_price in ingredients_info: - mock_ing = Mock(spec=Ingredient) - 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) - - receipt = burger.get_receipt() - receipt_lines = receipt.split('\n') - - # Проверяем, что все ожидаемые строки присутствуют в чеке (и в правильном порядке) - assert len(receipt_lines) == len(expected_lines), "Количество строк не совпадает" - for expected, actual in zip(expected_lines, receipt_lines): - assert expected == actual, f"Ожидалось: '{expected}', получено: '{actual}'" From 1279df9753c20439ea01804a2088af3c627b9cad Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 20:27:14 +0300 Subject: [PATCH 12/13] Create README.md --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..272081708 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +## Задание 1: Юнит-тесты + +### Автотесты для проверки программы, которая помогает заказать бургер в Stellar Burgers + +### Реализованные сценарии + +Созданы юнит-тесты, покрывающие классы `Bun`, `Burger`, `Ingredient`, `Database` + +Процент покрытия 100% (отчет: `htmlcov/index.html`) + +### Структура проекта + +- `praktikum` - пакет, содержащий код программы +- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. + +### Запуск автотестов + +**Установка зависимостей** + +> `$ pip install -r requirements.txt` + +**Запуск автотестов и создание HTML-отчета о покрытии** + +> `$ pytest --cov=praktikum --cov-report=html` From 118fae9e82614d02b13d192c5bb74ec07d04d18d Mon Sep 17 00:00:00 2001 From: kuzka1287-hue Date: Mon, 13 Jul 2026 20:43:44 +0300 Subject: [PATCH 13/13] Add files via upload --- __init__.py | 1 + bun.py | 15 ++++ burger.py | 48 ++++++++++++ database.py | 33 ++++++++ ingredient.py | 20 +++++ ingredient_types.py | 7 ++ praktikum.py | 41 ++++++++++ tests/test_burger.py | 181 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 346 insertions(+) create mode 100644 __init__.py create mode 100644 bun.py create mode 100644 burger.py create mode 100644 database.py create mode 100644 ingredient.py create mode 100644 ingredient_types.py create mode 100644 praktikum.py create mode 100644 tests/test_burger.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ + diff --git a/bun.py b/bun.py new file mode 100644 index 000000000..2d72ac989 --- /dev/null +++ b/bun.py @@ -0,0 +1,15 @@ +class Bun: + """ + Модель булочки для бургера. + Булочке можно дать название и назначить цену. + """ + + def __init__(self, name: str, price: float): + self.name = name + self.price = price + + def get_name(self) -> str: + return self.name + + def get_price(self) -> float: + return self.price diff --git a/burger.py b/burger.py new file mode 100644 index 000000000..c47ebe76a --- /dev/null +++ b/burger.py @@ -0,0 +1,48 @@ +from typing import List + +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient + + +class Burger: + """ + Модель бургера. + Бургер состоит из булочек и ингредиентов (начинка или соус). + Ингредиенты можно перемещать и удалять. + Можно распечать чек с информацией о бургере. + """ + + def __init__(self): + self.bun = None + self.ingredients: List[Ingredient] = [] + + def set_buns(self, bun: Bun): + self.bun = bun + + def add_ingredient(self, ingredient: Ingredient): + self.ingredients.append(ingredient) + + def remove_ingredient(self, index: int): + del self.ingredients[index] + + def move_ingredient(self, index: int, new_index: int): + self.ingredients.insert(new_index, self.ingredients.pop(index)) + + def get_price(self) -> float: + price = self.bun.get_price() * 2 + + for ingredient in self.ingredients: + price += ingredient.get_price() + + return price + + def get_receipt(self) -> str: + receipt: List[str] = [f'(==== {self.bun.get_name()} ====)'] + + for ingredient in self.ingredients: + receipt.append(f'= {str(ingredient.get_type()).lower()} {ingredient.get_name()} =') + + receipt.append(f'(==== {self.bun.get_name()} ====)\n') + receipt.append(f'Price: {self.get_price()}') + + return '\n'.join(receipt) diff --git a/database.py b/database.py new file mode 100644 index 000000000..675150c09 --- /dev/null +++ b/database.py @@ -0,0 +1,33 @@ +from typing import List + +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +class Database: + """ + Класс с методами по работе с базой данных. + """ + + def __init__(self): + self.buns: List[Bun] = [] + self.ingredients: List[Ingredient] = [] + + self.buns.append(Bun("black bun", 100)) + self.buns.append(Bun("white bun", 200)) + self.buns.append(Bun("red bun", 300)) + + self.ingredients.append(Ingredient(INGREDIENT_TYPE_SAUCE, "hot sauce", 100)) + self.ingredients.append(Ingredient(INGREDIENT_TYPE_SAUCE, "sour cream", 200)) + self.ingredients.append(Ingredient(INGREDIENT_TYPE_SAUCE, "chili sauce", 300)) + + self.ingredients.append(Ingredient(INGREDIENT_TYPE_FILLING, "cutlet", 100)) + self.ingredients.append(Ingredient(INGREDIENT_TYPE_FILLING, "dinosaur", 200)) + self.ingredients.append(Ingredient(INGREDIENT_TYPE_FILLING, "sausage", 300)) + + def available_buns(self) -> List[Bun]: + return self.buns + + def available_ingredients(self) -> List[Ingredient]: + return self.ingredients diff --git a/ingredient.py b/ingredient.py new file mode 100644 index 000000000..15b4dff57 --- /dev/null +++ b/ingredient.py @@ -0,0 +1,20 @@ +class Ingredient: + """ + Модель ингредиента. + Ингредиент: начинка или соус. + У ингредиента есть тип (начинка или соус), название и цена. + """ + + def __init__(self, ingredient_type: str, name: str, price: float): + self.type = ingredient_type + self.name = name + self.price = price + + def get_price(self) -> float: + return self.price + + def get_name(self) -> str: + return self.name + + def get_type(self) -> str: + return self.type diff --git a/ingredient_types.py b/ingredient_types.py new file mode 100644 index 000000000..00242d044 --- /dev/null +++ b/ingredient_types.py @@ -0,0 +1,7 @@ +""" +Перечисление с типами ингредиентов. +SAUCE – соус +FILLING – начинка +""" +INGREDIENT_TYPE_SAUCE = 'SAUCE' +INGREDIENT_TYPE_FILLING = 'FILLING' diff --git a/praktikum.py b/praktikum.py new file mode 100644 index 000000000..ef22bc592 --- /dev/null +++ b/praktikum.py @@ -0,0 +1,41 @@ +from typing import List + +from praktikum.bun import Bun +from praktikum.burger import Burger +from praktikum.database import Database +from praktikum.ingredient import Ingredient + + +def main(): + # Инициализируем базу данных + database: Database = Database() + + # Создадим новый бургер + burger: Burger = Burger() + + # Считаем список доступных булок из базы данных + buns: List[Bun] = database.available_buns() + + # Считаем список доступных ингредиентов из базы данных + ingredients: List[Ingredient] = database.available_ingredients() + + # Соберём бургер + burger.set_buns(buns[0]) + + burger.add_ingredient(ingredients[1]) + burger.add_ingredient(ingredients[4]) + burger.add_ingredient(ingredients[3]) + burger.add_ingredient(ingredients[5]) + + # Переместим слой с ингредиентом + burger.move_ingredient(2, 1) + + # Удалим ингредиент + burger.remove_ingredient(3) + + # Распечатаем рецепт бургера + print(burger.get_receipt()) + + +if __name__ == "__main__": + main() diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..edece89b3 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,181 @@ +# tests/test_burger.py + +import pytest +from unittest.mock import Mock +from praktikum.burger import Burger +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient + + +@pytest.fixture +def burger(): + """Фикстура, создающая новый экземпляр Burger перед каждым тестом.""" + return Burger() + + +# ---- Тесты для set_buns ---- + +def test_set_buns(burger): + """Проверяет, что метод set_buns сохраняет переданную булочку.""" + mock_bun = Mock(spec=Bun) + mock_bun.get_name.return_value = "white bun" + mock_bun.get_price.return_value = 100.0 + + burger.set_buns(mock_bun) + + assert burger.bun == mock_bun + assert burger.bun.get_name() == "white bun" + assert burger.bun.get_price() == 100.0 + + +# ---- Тесты для add_ingredient ---- + +def test_add_ingredient(burger): + """Проверяет добавление ингредиента в список.""" + mock_ingredient = Mock(spec=Ingredient) + mock_ingredient.get_name.return_value = "cutlet" + mock_ingredient.get_price.return_value = 150.0 + + burger.add_ingredient(mock_ingredient) + + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == mock_ingredient + assert burger.ingredients[0].get_name() == "cutlet" + + +# ---- Тесты для remove_ingredient ---- + +def test_remove_ingredient(burger): + """Проверяет удаление ингредиента по индексу.""" + ing1 = Mock(spec=Ingredient) + ing2 = Mock(spec=Ingredient) + burger.add_ingredient(ing1) + burger.add_ingredient(ing2) + + burger.remove_ingredient(0) + + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == ing2 + + +def test_remove_ingredient_out_of_range(burger): + """Проверяет, что при удалении по несуществующему индексу возникает IndexError.""" + with pytest.raises(IndexError): + burger.remove_ingredient(0) # список пуст + + +# ---- Тесты для move_ingredient ---- + +def test_move_ingredient(burger): + """Проверяет перемещение ингредиента с одного индекса на другой.""" + ing1 = Mock(spec=Ingredient) + ing2 = Mock(spec=Ingredient) + ing3 = Mock(spec=Ingredient) + burger.add_ingredient(ing1) + burger.add_ingredient(ing2) + burger.add_ingredient(ing3) + + burger.move_ingredient(0, 2) + + # Ожидаемый порядок: ing2, ing3, ing1 + assert burger.ingredients[0] == ing2 + assert burger.ingredients[1] == ing3 + assert burger.ingredients[2] == ing1 + + +def test_move_ingredient_out_of_range(burger): + """Проверяет, что при перемещении с невалидным индексом возникает IndexError.""" + with pytest.raises(IndexError): + burger.move_ingredient(5, 0) + + +# ---- Тесты для get_price (с параметризацией) ---- + +@pytest.mark.parametrize( + "bun_price, ingredient_prices, expected_price", + [ + (100.0, [50.0, 30.0], 280.0), # 2*100 + 50 + 30 = 280 + (200.0, [], 400.0), # только булочка (2*200) + (150.0, [10.0, 20.0, 30.0], 360.0), # 2*150 + 10+20+30 = 360 + ] +) +def test_get_price(burger, bun_price, ingredient_prices, expected_price): + """Параметризованный тест: проверяет расчёт цены бургера с разными наборами ингредиентов.""" + mock_bun = Mock(spec=Bun) + mock_bun.get_price.return_value = bun_price + burger.set_buns(mock_bun) + + for price in ingredient_prices: + mock_ing = Mock(spec=Ingredient) + mock_ing.get_price.return_value = price + burger.add_ingredient(mock_ing) + + assert burger.get_price() == expected_price + + +# ---- Тесты для get_receipt (с параметризацией) ---- + +@pytest.mark.parametrize( + "bun_name, bun_price, ingredients_info, expected_lines", + [ + # Случай без ингредиентов + ( + "black bun", + 100.0, + [], + [ + "(==== black bun ====)", + "(==== black bun ====)", + "Price: 200.0" + ] + ), + # С одним соусом + ( + "white bun", + 200.0, + [("SAUCE", "hot sauce", 100.0)], + [ + "(==== white bun ====)", + "= sauce hot sauce =", + "(==== white bun ====)", + "Price: 500.0" # 2*200 + 100 = 500 + ] + ), + # С двумя ингредиентами (соус и начинка) + ( + "red bun", + 300.0, + [ + ("SAUCE", "chili sauce", 50.0), + ("FILLING", "cutlet", 150.0) + ], + [ + "(==== red bun ====)", + "= sauce chili sauce =", + "= filling cutlet =", + "(==== red bun ====)", + "Price: 800.0" # 2*300 + 50 + 150 = 800 + ] + ), + ] +) +def test_get_receipt(burger, bun_name, bun_price, ingredients_info, expected_lines): + """Параметризованный тест: проверяет формирование чека для разных составов.""" + mock_bun = Mock(spec=Bun) + mock_bun.get_name.return_value = bun_name + mock_bun.get_price.return_value = bun_price + burger.set_buns(mock_bun) + + for ing_type, ing_name, ing_price in ingredients_info: + mock_ing = Mock(spec=Ingredient) + 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) + + receipt = burger.get_receipt() + receipt_lines = receipt.split('\n') + + assert len(receipt_lines) == len(expected_lines), "Количество строк не совпадает" + for expected, actual in zip(expected_lines, receipt_lines): + assert expected == actual, f"Ожидалось: '{expected}', получено: '{actual}'" \ No newline at end of file