diff --git a/praktikum/__init__.py b/praktikum/__init__.py new file mode 100644 index 000000000..e69de29bb 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..2b3b6a88b --- /dev/null +++ b/praktikum/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/praktikum/database.py b/praktikum/database.py new file mode 100644 index 000000000..4c75baf71 --- /dev/null +++ b/praktikum/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/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/praktikum/praktikum.py b/praktikum/praktikum.py new file mode 100644 index 000000000..ec522fa6d --- /dev/null +++ b/praktikum/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/requirements.txt b/requirements.txt new file mode 100644 index 000000000..ac4f60912 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pytest==7.4.3 +pytest-cov==7.0.0 +allure-pytest==2.13.2 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/burger_test.py b/tests/burger_test.py new file mode 100644 index 000000000..cebcb8455 --- /dev/null +++ b/tests/burger_test.py @@ -0,0 +1,131 @@ +import pytest +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from helpers import ( + create_mock_ingredient, + create_mock_bun, + add_multiple_ingredients_to_burger, + calculate_burger_price, + create_ingredients_list +) +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +class TestBurger: + + def test_set_bun_success(self, burger, white_bun): + burger.set_buns(white_bun) + assert burger.bun == white_bun + + def test_set_bun_replace_existing(self, burger, white_bun, black_bun): + burger.set_buns(white_bun) + burger.set_buns(black_bun) + assert burger.bun == black_bun + + def test_add_ingredient_one(self, burger, sauce_ingredient): + burger.add_ingredient(sauce_ingredient) + assert len(burger.ingredients) == 1 + + def test_add_ingredient_first_element(self, burger, sauce_ingredient): + burger.add_ingredient(sauce_ingredient) + assert burger.ingredients[0] == sauce_ingredient + + def test_add_ingredient_multiple(self, burger, ingredient_count): + ingredients = create_ingredients_list(ingredient_count, INGREDIENT_TYPE_SAUCE, 50.0) + burger = add_multiple_ingredients_to_burger(burger, ingredients) + assert len(burger.ingredients) == ingredient_count + + def test_add_ingredient_keeps_order_first(self, burger, sauce_ingredient, filling_ingredient): + burger.add_ingredient(sauce_ingredient) + burger.add_ingredient(filling_ingredient) + assert burger.ingredients[0] == sauce_ingredient + + def test_add_ingredient_keeps_order_second(self, burger, sauce_ingredient, filling_ingredient): + burger.add_ingredient(sauce_ingredient) + burger.add_ingredient(filling_ingredient) + assert burger.ingredients[1] == filling_ingredient + + + def test_remove_ingredient_decreases_count(self, burger): + ing1 = create_mock_ingredient("First", INGREDIENT_TYPE_SAUCE, 50.0) + ing2 = create_mock_ingredient("Second", INGREDIENT_TYPE_FILLING, 100.0) + burger = add_multiple_ingredients_to_burger(burger, [ing1, ing2]) + burger.remove_ingredient(0) + assert len(burger.ingredients) == 1 + + def test_remove_ingredient_first_leaves_second(self, burger): + ing1 = create_mock_ingredient("First", INGREDIENT_TYPE_SAUCE, 50.0) + ing2 = create_mock_ingredient("Second", INGREDIENT_TYPE_FILLING, 100.0) + burger = add_multiple_ingredients_to_burger(burger, [ing1, ing2]) + burger.remove_ingredient(0) + assert burger.ingredients[0] == ing2 + + def test_remove_ingredient_middle(self, burger): + ing1 = create_mock_ingredient("First", INGREDIENT_TYPE_SAUCE, 50.0) + ing2 = create_mock_ingredient("Middle", INGREDIENT_TYPE_FILLING, 100.0) + ing3 = create_mock_ingredient("Last", INGREDIENT_TYPE_SAUCE, 75.0) + burger = add_multiple_ingredients_to_burger(burger, [ing1, ing2, ing3]) + burger.remove_ingredient(1) + assert burger.ingredients[1] == ing3 + + def test_remove_ingredient_at_index(self, burger): + ingredients = create_ingredients_list(3, INGREDIENT_TYPE_SAUCE, 50.0) + burger = add_multiple_ingredients_to_burger(burger, ingredients) + burger.remove_ingredient(0) + assert len(burger.ingredients) == 2 + + def test_move_ingredient_forward(self, burger): + ing1 = create_mock_ingredient("First", INGREDIENT_TYPE_SAUCE, 50.0) + ing2 = create_mock_ingredient("Second", INGREDIENT_TYPE_FILLING, 100.0) + ing3 = create_mock_ingredient("Third", INGREDIENT_TYPE_SAUCE, 75.0) + burger = add_multiple_ingredients_to_burger(burger, [ing1, ing2, ing3]) + burger.move_ingredient(0, 2) + assert burger.ingredients[2] == ing1 + + def test_move_ingredient_changes_first_position(self, burger): + ing1 = create_mock_ingredient("First", INGREDIENT_TYPE_SAUCE, 50.0) + ing2 = create_mock_ingredient("Second", INGREDIENT_TYPE_FILLING, 100.0) + ing3 = create_mock_ingredient("Third", INGREDIENT_TYPE_SAUCE, 75.0) + burger = add_multiple_ingredients_to_burger(burger, [ing1, ing2, ing3]) + burger.move_ingredient(0, 2) + assert burger.ingredients[0] == ing2 + + def test_move_ingredient_backward(self, burger): + ing1 = create_mock_ingredient("First", INGREDIENT_TYPE_SAUCE, 50.0) + ing2 = create_mock_ingredient("Second", INGREDIENT_TYPE_FILLING, 100.0) + ing3 = create_mock_ingredient("Third", INGREDIENT_TYPE_SAUCE, 75.0) + burger = add_multiple_ingredients_to_burger(burger, [ing1, ing2, ing3]) + burger.move_ingredient(2, 0) + assert burger.ingredients[0] == ing3 + + def test_move_ingredient_preserves_count(self, burger): + + ingredients = create_ingredients_list(3, INGREDIENT_TYPE_SAUCE, 50.0) + burger = add_multiple_ingredients_to_burger(burger, ingredients) + burger.move_ingredient(0, 2) + assert len(burger.ingredients) == 3 + + + @pytest.mark.parametrize("bun_price,ingredient_prices,expected_price", [ + (100.0, [50.0], 250.0), + (150.0, [50.0, 75.0], 425.0), + (200.0, [100.0, 100.0, 100.0], 700.0), + ]) + def test_price_calculation(self, burger, bun_price, ingredient_prices, expected_price): + calculated_price = calculate_burger_price(bun_price, ingredient_prices) + assert calculated_price == expected_price + + + @pytest.mark.parametrize("from_pos,to_pos", [ + (0, 1), (1, 0), (0, 2), (2, 0) + ]) + def test_move_ingredient_parametrized(self, burger, from_pos, to_pos): + ingredients = create_ingredients_list(3, INGREDIENT_TYPE_SAUCE, 50.0) + burger = add_multiple_ingredients_to_burger(burger, ingredients) + original_ingredient = burger.ingredients[from_pos] + burger.move_ingredient(from_pos, to_pos) + assert burger.ingredients[to_pos] == original_ingredient \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..1ad8b411f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,34 @@ +import pytest +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from helpers import create_mock_ingredient, create_mock_bun +from praktikum.burger import Burger +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + +@pytest.fixture +def burger(): + return Burger() + +@pytest.fixture +def white_bun(): + return create_mock_bun("White Bun", 100.0) + +@pytest.fixture +def black_bun(): + return create_mock_bun("Black Bun", 150.0) + +@pytest.fixture +def sauce_ingredient(): + return create_mock_ingredient("spicy sauce", INGREDIENT_TYPE_SAUCE, 90.0) + +@pytest.fixture +def filling_ingredient(): + return create_mock_ingredient("Cutlet", INGREDIENT_TYPE_FILLING, 100.0) + +@pytest.fixture(params=[1, 3, 5, 10]) +def ingredient_count(request): + return request.param diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 000000000..080cdf514 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,52 @@ +from unittest.mock import Mock +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +def create_mock_ingredient(name: str, ingredient_type: str, price: float) -> Mock: + ingredient = Mock() + ingredient.get_name = Mock(return_value=name) + ingredient.get_type = Mock(return_value=ingredient_type) + ingredient.get_price = Mock(return_value=price) + return ingredient + + +def create_mock_bun(name: str, price: float) -> Mock: + bun = Mock() + bun.get_name = Mock(return_value=name) + bun.get_price = Mock(return_value=price) + return bun + + +def setup_burger_with_bun_and_ingredients(burger, bun, *ingredients): + burger.set_buns(bun) + for ingredient in ingredients: + burger.add_ingredient(ingredient) + return burger + + +def add_multiple_ingredients_to_burger(burger, ingredient_list): + for ingredient in ingredient_list: + burger.add_ingredient(ingredient) + return burger + + +def calculate_burger_price(bun_price: float, ingredient_prices: list) -> float: + return (bun_price * 2) + sum(ingredient_prices) + + +def extract_price_from_receipt(receipt: str) -> float: + price_line = [line for line in receipt.split('\n') if line.startswith('Price:')] + if price_line: + return float(price_line[0].replace('Price: ', '')) + return 0.0 + + +def verify_ingredients_in_receipt(receipt: str, ingredient_names: list) -> bool: + return all(name in receipt for name in ingredient_names) + + +def create_ingredients_list(count: int, ingredient_type: str, price: float) -> list: + return [ + create_mock_ingredient(f"Ingredient_{i}", ingredient_type, price) + for i in range(count) + ]