diff --git a/README.md b/README.md index 272081708..808865fe4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ### Структура проекта - `praktikum` - пакет, содержащий код программы -- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. +- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `test_bun.py`, `test_burger.py` и т.д. ### Запуск автотестов diff --git a/praktikum/bun.py b/praktikum/bun.py new file mode 100644 index 000000000..5504bc1f4 --- /dev/null +++ b/praktikum/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/praktikum/burger.py b/praktikum/burger.py new file mode 100644 index 000000000..a33cc7c4f --- /dev/null +++ b/praktikum/burger.py @@ -0,0 +1,48 @@ +from typing import List + +from .bun import Bun +from .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/praktikum/database.py b/praktikum/database.py new file mode 100644 index 000000000..b99c35512 --- /dev/null +++ b/praktikum/database.py @@ -0,0 +1,33 @@ +from typing import List + +from .bun import Bun +from .ingredient import Ingredient +from .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/praktikum/ingredient.py b/praktikum/ingredient.py new file mode 100644 index 000000000..0e50db8a2 --- /dev/null +++ b/praktikum/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/praktikum/ingredient_types.py b/praktikum/ingredient_types.py new file mode 100644 index 000000000..34940ad5d --- /dev/null +++ b/praktikum/ingredient_types.py @@ -0,0 +1,7 @@ +""" +Перечисление с типами ингредиентов. +SAUCE – соус +FILLING – начинка +""" +INGREDIENT_TYPE_SAUCE = 'SAUCE' +INGREDIENT_TYPE_FILLING = 'FILLING' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..1fe7b5eb9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest==8.3.3 +pytest-cov==5.0.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..afdb0eaaf --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +import os +import sys + +import pytest +from unittest.mock import Mock + + +# Ensure the project root (where the `praktikum/` package lives) is importable. +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +if PROJECT_ROOT not in sys.path: + sys.path.insert(0, PROJECT_ROOT) + + +@pytest.fixture +def bun_mock(): + bun = Mock() + bun.get_name.return_value = 'test bun' + bun.get_price.return_value = 50.0 + return bun + + +@pytest.fixture +def ingredient_mock(): + ingr = Mock() + ingr.get_type.return_value = 'SAUCE' + ingr.get_name.return_value = 'hot sauce' + ingr.get_price.return_value = 10.0 + return ingr diff --git a/tests/test_bun.py b/tests/test_bun.py new file mode 100644 index 000000000..7c9b7ff03 --- /dev/null +++ b/tests/test_bun.py @@ -0,0 +1,34 @@ +import pytest + +from praktikum.bun import Bun + + +@pytest.mark.parametrize( + 'name', + [ + 'black bun', + 'булочка', + 'bun-123', + 'bun!@#$', + ], +) +def test_get_name_returns_init_value(name): + bun = Bun(name, 100.0) + + assert bun.get_name() == name + + +@pytest.mark.parametrize( + 'price', + [ + 0, + 1, + 200.5, + 1_000_000.0, + -10.5, + ], +) +def test_get_price_returns_init_value(price): + bun = Bun('black bun', price) + + assert bun.get_price() == price diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..457654360 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,82 @@ +from unittest.mock import Mock + +import pytest + +from praktikum.burger import Burger + + +def test_set_buns_sets_bun_reference(bun_mock): + burger = Burger() + + burger.set_buns(bun_mock) + + assert burger.bun is bun_mock + + +def test_add_ingredient_appends_to_list(ingredient_mock): + burger = Burger() + + burger.add_ingredient(ingredient_mock) + + assert burger.ingredients == [ingredient_mock] + + +def test_remove_ingredient_deletes_by_index(): + burger = Burger() + ing1, ing2 = Mock(), Mock() + burger.ingredients = [ing1, ing2] + + burger.remove_ingredient(0) + + assert burger.ingredients == [ing2] + + +def test_move_ingredient_reorders_list(): + burger = Burger() + ing1, ing2, ing3 = Mock(), Mock(), Mock() + burger.ingredients = [ing1, ing2, ing3] + + burger.move_ingredient(0, 2) + + assert burger.ingredients == [ing2, ing3, ing1] + + +def test_get_price_sums_bun_double_and_ingredients(bun_mock): + burger = Burger() + burger.set_buns(bun_mock) + + ing1, ing2 = Mock(), Mock() + ing1.get_price.return_value = 10.0 + ing2.get_price.return_value = 15.5 + burger.ingredients = [ing1, ing2] + + assert burger.get_price() == 50.0 * 2 + 10.0 + 15.5 + + +def test_get_receipt_formats_lines_and_uses_lowercase_types(bun_mock): + burger = Burger() + burger.set_buns(bun_mock) + + sauce = Mock() + sauce.get_type.return_value = 'SAUCE' + sauce.get_name.return_value = 'hot sauce' + sauce.get_price.return_value = 10.0 + + filling = Mock() + filling.get_type.return_value = 'FILLING' + filling.get_name.return_value = 'cutlet' + filling.get_price.return_value = 20.0 + + burger.ingredients = [sauce, filling] + + receipt = burger.get_receipt() + + expected = ( + '(==== test bun ====)\n' + '= sauce hot sauce =\n' + '= filling cutlet =\n' + '(==== test bun ====)\n\n' + 'Price: 130.0' + ) + + assert receipt == expected diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 000000000..e60f13707 --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,34 @@ +from praktikum.database import Database +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +def test_available_buns_returns_list_of_buns(): + db = Database() + + buns = db.available_buns() + + assert isinstance(buns, list) + assert buns, 'Ожидался непустой список булочек' + assert all(isinstance(bun, Bun) for bun in buns) + + # Проверяем, что возвращаемые объекты можно корректно использовать через публичные методы + for bun in buns: + assert isinstance(bun.get_name(), str) + assert isinstance(bun.get_price(), (int, float)) + + +def test_available_ingredients_returns_list_of_ingredients(): + db = Database() + + ingredients = db.available_ingredients() + + assert isinstance(ingredients, list) + assert ingredients, 'Ожидался непустой список ингредиентов' + assert all(isinstance(ingredient, Ingredient) for ingredient in ingredients) + + for ingredient in ingredients: + assert ingredient.get_type() in (INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING) + assert isinstance(ingredient.get_name(), str) + assert isinstance(ingredient.get_price(), (int, float)) diff --git a/tests/test_ingredient.py b/tests/test_ingredient.py new file mode 100644 index 000000000..3d309c346 --- /dev/null +++ b/tests/test_ingredient.py @@ -0,0 +1,48 @@ +import pytest + +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +@pytest.mark.parametrize( + 'ingredient_type', + [ + INGREDIENT_TYPE_SAUCE, + INGREDIENT_TYPE_FILLING, + ], +) +def test_get_type_returns_init_value(ingredient_type): + ingredient = Ingredient(ingredient_type, 'test ingredient', 100.0) + + assert ingredient.get_type() == ingredient_type + + +@pytest.mark.parametrize( + 'name', + [ + 'hot sauce', + 'котлета', + 'ingredient-123', + 'ingr!@#$', + ], +) +def test_get_name_returns_init_value(name): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, name, 100.0) + + assert ingredient.get_name() == name + + +@pytest.mark.parametrize( + 'price', + [ + 0, + 1, + 300.5, + 1_000_000.0, + -10.5, + ], +) +def test_get_price_returns_init_value(price): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, 'test ingredient', price) + + assert ingredient.get_price() == price