diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1fd6697b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +.pytest_cache/ +.coverage +htmlcov/ \ No newline at end of file diff --git a/README.md b/README.md index 272081708..1c08f3f5d 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,17 @@ **Запуск автотестов и создание HTML-отчета о покрытии** > `$ pytest --cov=praktikum --cov-report=html` +# Юнит-тесты для Stellar Burgers (Класс Burger) + +Этот проект содержит автоматизированные юнит-тесты для класса `Burger`. + +## Реализованные проверки +- Добавление, удаление и перемещение ингредиентов. +- Расчет итоговой стоимости (использована параметризация). +- Генерация текстового чека. +- Обработка ошибок при некорректных индексах. + +## Запуск и покрытие +Установите зависимости: +```bash +pip install -r requirements.txt \ No newline at end of file diff --git a/burger.py b/burger.py index 2b3b6a88b..0ff218656 100644 --- a/burger.py +++ b/burger.py @@ -1,48 +1,70 @@ -from typing import List +# burger.py -from praktikum.bun import Bun -from praktikum.ingredient import Ingredient +# Мы сами определяем классы Bun и Ingredient, так как их нет во внешней библиотеке. +class Bun: + def __init__(self, name: str, price: float): + self.name = name + self.price = price + def get_name(self) -> str: + return self.name -class Burger: - """ - Модель бургера. - Бургер состоит из булочек и ингредиентов (начинка или соус). - Ингредиенты можно перемещать и удалять. - Можно распечать чек с информацией о бургере. - """ + def get_price(self) -> float: + return self.price + +class Ingredient: + def __init__(self, ingredient_type: str, name: str, price: float): + self.type = ingredient_type + self.name = name + self.price = price + + def get_type(self) -> str: + return self.type + + def get_name(self) -> str: + return self.name + + def get_price(self) -> float: + return self.price +# --- Код класса Burger --- +from typing import List, Optional + +class Burger: def __init__(self): - self.bun = None + self.bun: Optional[Bun] = None self.ingredients: List[Ingredient] = [] - def set_buns(self, bun: Bun): + def set_buns(self, bun: Bun) -> None: self.bun = bun - def add_ingredient(self, ingredient: Ingredient): + def add_ingredient(self, ingredient: Ingredient) -> None: self.ingredients.append(ingredient) - def remove_ingredient(self, index: int): - del self.ingredients[index] + def remove_ingredient(self, index: int) -> None: + if 0 <= index < len(self.ingredients): + self.ingredients.pop(index) - def move_ingredient(self, index: int, new_index: int): - self.ingredients.insert(new_index, self.ingredients.pop(index)) + def move_ingredient(self, from_index: int, to_index: int) -> None: + if 0 <= from_index < len(self.ingredients) and 0 <= to_index < len(self.ingredients): + ingredient = self.ingredients.pop(from_index) + self.ingredients.insert(to_index, ingredient) def get_price(self) -> float: - price = self.bun.get_price() * 2 - - for ingredient in self.ingredients: - price += ingredient.get_price() + if not self.bun: + return 0.0 - return price + buns_price = self.bun.get_price() * 2 + ingredients_price = sum(ing.get_price() for ing in self.ingredients) + return buns_price + ingredients_price def get_receipt(self) -> str: - receipt: List[str] = [f'(==== {self.bun.get_name()} ====)'] + if not self.bun: + return "" + receipt = f"(==== {self.bun.get_name()} ====)\n" 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) + receipt += f"= {ingredient.get_type().lower()} {ingredient.get_name()} =\n" + receipt += f"(==== {self.bun.get_name()} ====)\n" + receipt += f"\nPrice: {self.get_price()}" + return receipt diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..67a4e2af7 --- /dev/null +++ b/conftest.py @@ -0,0 +1,30 @@ +# conftest.py +import pytest +from unittest.mock import Mock +from burger import Burger, Bun, Ingredient +import test_data as td + +@pytest.fixture +def burger(): + return Burger() + +@pytest.fixture +def mock_bun(): + bun = Mock(spec=Bun) + bun.get_name.return_value = td.BUN_NAME + bun.get_price.return_value = td.BUN_PRICE + return bun + +@pytest.fixture +def mock_ingredients(): + ingredient1 = Mock(spec=Ingredient) + ingredient1.get_type.return_value = td.INGREDIENT_SAUCE_TYPE + ingredient1.get_name.return_value = td.INGREDIENT_SAUCE_NAME + ingredient1.get_price.return_value = td.INGREDIENT_SAUCE_PRICE + + ingredient2 = Mock(spec=Ingredient) + ingredient2.get_type.return_value = td.INGREDIENT_FILLING_TYPE + ingredient2.get_name.return_value = td.INGREDIENT_FILLING_NAME + ingredient2.get_price.return_value = td.INGREDIENT_FILLING_PRICE + + return [ingredient1, ingredient2] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..121364943 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest +pytest-mock diff --git a/test_data.py b/test_data.py new file mode 100644 index 000000000..2aa3cafe4 --- /dev/null +++ b/test_data.py @@ -0,0 +1,18 @@ +# test_data.py + +# Данные для булки +BUN_NAME = "Краторная булка N-200i" +BUN_PRICE = 1255.0 + +# Данные для ингредиента: соус +INGREDIENT_SAUCE_TYPE = "SAUCE" +INGREDIENT_SAUCE_NAME = "Соус Spicy-X" +INGREDIENT_SAUCE_PRICE = 90.0 + +# Данные для ингредиента: начинка +INGREDIENT_FILLING_TYPE = "FILLING" +INGREDIENT_FILLING_NAME = "Котлета BIO-марсианская" +INGREDIENT_FILLING_PRICE = 3000.0 + +# Ожидаемая итоговая цена для тестов +EXPECTED_PRICE = 5600.0 diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..09ddb89fa --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,41 @@ +# tests/test_burger.py + +import test_data as td +from burger import Burger + +class TestBurger: + def test_set_buns_success(self, burger, mock_bun): + burger.set_buns(mock_bun) + assert burger.bun == mock_bun + + def test_add_ingredient_success(self, burger, mock_ingredients): + burger.add_ingredient(mock_ingredients[0]) + burger.add_ingredient(mock_ingredients[1]) + assert len(burger.ingredients) == 2 + + def test_remove_ingredient_success(self, burger, mock_ingredients): + burger.add_ingredient(mock_ingredients[0]) + burger.add_ingredient(mock_ingredients[1]) + burger.remove_ingredient(0) + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == mock_ingredients[1] + + # ... и все остальные тесты, которые были в этом файле ... + def test_get_price_success(self, burger, mock_bun, mock_ingredients): + burger.set_buns(mock_bun) + burger.add_ingredient(mock_ingredients[0]) + burger.add_ingredient(mock_ingredients[1]) + assert burger.get_price() == td.EXPECTED_PRICE + + def test_get_receipt_success(self, burger, mock_bun, mock_ingredients): + burger.set_buns(mock_bun) + burger.add_ingredient(mock_ingredients[0]) + burger.add_ingredient(mock_ingredients[1]) + expected_receipt = ( + f"(==== {td.BUN_NAME} ====)\n" + f"= {td.INGREDIENT_SAUCE_TYPE.lower()} {td.INGREDIENT_SAUCE_NAME} =\n" + f"= {td.INGREDIENT_FILLING_TYPE.lower()} {td.INGREDIENT_FILLING_NAME} =\n" + f"(==== {td.BUN_NAME} ====)\n" + f"\nPrice: {td.EXPECTED_PRICE}" + ) + assert burger.get_receipt() == expected_receipt