From 22ea3c0d20f87af39e7c2e07b7f4cb9ebb0996db Mon Sep 17 00:00:00 2001 From: n1kolay-zhurkin Date: Sun, 8 Mar 2026 01:20:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=8E=D0=BD=D0=B8=D1=82-=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B8=20=D1=84=D0=B8=D0=BA=D1=81=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20Stellar=20Burgers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 41 ++++++ __init__.py => praktikum/__init__.py | 0 bun.py => praktikum/bun.py | 0 burger.py => praktikum/burger.py | 0 database.py => praktikum/database.py | 0 ingredient.py => praktikum/ingredient.py | 0 .../ingredient_types.py | 0 praktikum.py => praktikum/praktikum.py | 0 requirements.txt | 0 tests/__init__.py | 0 tests/conftest.py | 122 ++++++++++++++++++ tests/test_bun.py | 23 ++++ tests/test_burger.py | 98 ++++++++++++++ tests/test_database.py | 58 +++++++++ tests/test_ingredient.py | 28 ++++ 15 files changed, 370 insertions(+) create mode 100644 .gitignore rename __init__.py => praktikum/__init__.py (100%) rename bun.py => praktikum/bun.py (100%) rename burger.py => praktikum/burger.py (100%) rename database.py => praktikum/database.py (100%) rename ingredient.py => praktikum/ingredient.py (100%) rename ingredient_types.py => praktikum/ingredient_types.py (100%) rename praktikum.py => praktikum/praktikum.py (100%) create mode 100644 requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_bun.py create mode 100644 tests/test_burger.py create mode 100644 tests/test_database.py create mode 100644 tests/test_ingredient.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8f5644f42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Виртуальное окружение +venv/ +env/ +.env/ +.venv/ + +# Кэши Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ + +# Отчёты о покрытии +htmlcov/ +.coverage +.coverage.* +*.cover +*.py,cover + +# Файлы IDE (VS Code, PyCharm и др.) +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Логи и временные файлы +*.log +*.tmp +*.temp + +# Системные файлы (Windows) +Thumbs.db +ehthumbs.db +Desktop.ini + +# Файлы с секретами (если появятся) +.env +*.key +*.pem \ No newline at end of file diff --git a/__init__.py b/praktikum/__init__.py similarity index 100% rename from __init__.py rename to praktikum/__init__.py diff --git a/bun.py b/praktikum/bun.py similarity index 100% rename from bun.py rename to praktikum/bun.py diff --git a/burger.py b/praktikum/burger.py similarity index 100% rename from burger.py rename to praktikum/burger.py diff --git a/database.py b/praktikum/database.py similarity index 100% rename from database.py rename to praktikum/database.py diff --git a/ingredient.py b/praktikum/ingredient.py similarity index 100% rename from ingredient.py rename to praktikum/ingredient.py diff --git a/ingredient_types.py b/praktikum/ingredient_types.py similarity index 100% rename from ingredient_types.py rename to praktikum/ingredient_types.py diff --git a/praktikum.py b/praktikum/praktikum.py similarity index 100% rename from praktikum.py rename to praktikum/praktikum.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..7b4f5c9a3 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,122 @@ +import pytest +from unittest.mock import create_autospec + +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.database import Database +from praktikum.burger import Burger +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +# ------------------------------------------------------------------- +# Фикстуры для конкретных объектов (реальные или моки) +# ------------------------------------------------------------------- + +@pytest.fixture +def black_bun(): + """Реальная булочка black bun.""" + return Bun("black bun", 100) + + +@pytest.fixture +def white_bun(): + """Реальная булочка white bun.""" + return Bun("white bun", 200) + + +@pytest.fixture +def red_bun(): + """Реальная булочка red bun.""" + return Bun("red bun", 300) + + +@pytest.fixture +def hot_sauce(): + """Реальный соус hot sauce.""" + return Ingredient(INGREDIENT_TYPE_SAUCE, "hot sauce", 100) + + +@pytest.fixture +def sour_cream(): + """Реальный соус sour cream.""" + return Ingredient(INGREDIENT_TYPE_SAUCE, "sour cream", 200) + + +@pytest.fixture +def chili_sauce(): + """Реальный соус chili sauce.""" + return Ingredient(INGREDIENT_TYPE_SAUCE, "chili sauce", 300) + + +@pytest.fixture +def cutlet(): + """Реальная начинка cutlet.""" + return Ingredient(INGREDIENT_TYPE_FILLING, "cutlet", 100) + + +@pytest.fixture +def dinosaur(): + """Реальная начинка dinosaur.""" + return Ingredient(INGREDIENT_TYPE_FILLING, "dinosaur", 200) + + +@pytest.fixture +def sausage(): + """Реальная начинка sausage.""" + return Ingredient(INGREDIENT_TYPE_FILLING, "sausage", 300) + + +# ------------------------------------------------------------------- +# Фикстуры для моков (используются в тестах Burger) +# ------------------------------------------------------------------- + +@pytest.fixture +def mock_bun(): + """Мок булочки с предопределёнными значениями.""" + bun = create_autospec(Bun, instance=True) + bun.get_name.return_value = "black bun" + bun.get_price.return_value = 100 + return bun + + +@pytest.fixture +def mock_ingredient_sauce(): + """Мок соуса.""" + ing = create_autospec(Ingredient, instance=True) + ing.get_type.return_value = INGREDIENT_TYPE_SAUCE + ing.get_name.return_value = "hot sauce" + ing.get_price.return_value = 200 + return ing + + +@pytest.fixture +def mock_ingredient_filling(): + """Мок начинки.""" + ing = create_autospec(Ingredient, instance=True) + ing.get_type.return_value = INGREDIENT_TYPE_FILLING + ing.get_name.return_value = "cutlet" + ing.get_price.return_value = 300 + return ing + + +# ------------------------------------------------------------------- +# Фикстуры для сложных объектов +# ------------------------------------------------------------------- + +@pytest.fixture +def database(): + """Экземпляр базы данных.""" + return Database() + + +@pytest.fixture +def burger(): + """Пустой бургер.""" + return Burger() + + +@pytest.fixture +def burger_with_bun(burger, mock_bun): + """Бургер с установленной булкой (мок).""" + burger.set_buns(mock_bun) + return burger \ No newline at end of file diff --git a/tests/test_bun.py b/tests/test_bun.py new file mode 100644 index 000000000..281537120 --- /dev/null +++ b/tests/test_bun.py @@ -0,0 +1,23 @@ +import pytest +from praktikum.bun import Bun + + +class TestBun: + + @pytest.mark.parametrize("name, price", [ + ("black bun", 100), + ("white bun", 200), + ("red bun", 300), + ("", 0), + ("super bun", 99.9) + ]) + def test_bun_creation(self, name, price): + bun = Bun(name, price) + assert bun.name == name + assert bun.price == price + + def test_get_name(self, black_bun): + assert black_bun.get_name() == "black bun" + + def test_get_price(self, black_bun): + assert black_bun.get_price() == 100 \ No newline at end of file diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..6e7676d87 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,98 @@ +import pytest +from unittest.mock import Mock + +from praktikum.burger import Burger +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient + + +class TestBurger: + + def test_set_buns(self, burger, mock_bun): + burger.set_buns(mock_bun) + assert burger.bun == mock_bun + + def test_add_ingredient(self, burger, mock_ingredient_sauce): + burger.add_ingredient(mock_ingredient_sauce) + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == mock_ingredient_sauce + + def test_remove_ingredient(self, burger): + ing1, ing2 = Mock(spec=Ingredient), Mock(spec=Ingredient) + burger.ingredients = [ing1, ing2] + burger.remove_ingredient(0) + assert burger.ingredients == [ing2] + + def test_remove_ingredient_invalid_index_raises_error(self, burger): + burger.ingredients = [Mock(spec=Ingredient)] + with pytest.raises(IndexError): + burger.remove_ingredient(5) + + def test_move_ingredient(self, burger): + ing1, ing2, ing3 = Mock(spec=Ingredient), Mock(spec=Ingredient), Mock(spec=Ingredient) + burger.ingredients = [ing1, ing2, ing3] + burger.move_ingredient(0, 2) + assert burger.ingredients == [ing2, ing3, ing1] + + def test_move_ingredient_invalid_source_index_raises_error(self, burger): + burger.ingredients = [Mock(spec=Ingredient), Mock(spec=Ingredient)] + with pytest.raises(IndexError): + burger.move_ingredient(5, 0) # неверный исходный индекс + + def test_move_ingredient_valid_source_with_invalid_target_does_not_raise(self, burger): + burger.ingredients = [Mock(spec=Ingredient), Mock(spec=Ingredient)] + # Не должно быть исключения, т.к. insert при выходе за границы просто добавляет в конец + burger.move_ingredient(0, 5) + + @pytest.mark.parametrize( + "bun_price, ingredient_prices, expected_price", + [ + (50, [10, 20], 130.0), + (100, [], 200.0), + (30, [15, 25, 35], 135.0), + (0, [10, 10], 20.0), + (50.5, [10.2, 20.3], 131.5), + ] + ) + def test_get_price(self, 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 + + def test_get_receipt(self, burger_with_bun, mock_ingredient_sauce, mock_ingredient_filling): + burger_with_bun.add_ingredient(mock_ingredient_sauce) + burger_with_bun.add_ingredient(mock_ingredient_filling) + + expected_receipt = ( + "(==== black bun ====)\n" + "= sauce hot sauce =\n" + "= filling cutlet =\n" + "(==== black bun ====)\n" + "\n" + "Price: 700" # убрали .0 + ) + assert burger_with_bun.get_receipt() == expected_receipt + + def test_receipt_without_ingredients(self, burger_with_bun): + expected_receipt = ( + "(==== black bun ====)\n" + "(==== black bun ====)\n" + "\n" + "Price: 200" # убрали .0 + ) + assert burger_with_bun.get_receipt() == expected_receipt + + def test_price_without_bun_raises_error(self, burger): + with pytest.raises(AttributeError): + burger.get_price() + + def test_receipt_without_bun_raises_error(self, burger): + with pytest.raises(AttributeError): + burger.get_receipt() \ No newline at end of file diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 000000000..610becaa6 --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,58 @@ +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +class TestDatabase: + + def test_initial_buns_count(self, database): + buns = database.available_buns() + assert len(buns) == 3 + + def test_initial_ingredients_count(self, database): + ingredients = database.available_ingredients() + assert len(ingredients) == 6 # 3 соуса + 3 начинки + + def test_buns_content(self, database, black_bun, white_bun, red_bun): + buns = database.available_buns() + # Проверяем через сравнение атрибутов, так как это разные объекты + assert buns[0].get_name() == black_bun.get_name() + assert buns[0].get_price() == black_bun.get_price() + assert buns[1].get_name() == white_bun.get_name() + assert buns[1].get_price() == white_bun.get_price() + assert buns[2].get_name() == red_bun.get_name() + assert buns[2].get_price() == red_bun.get_price() + + def test_ingredients_content(self, database, hot_sauce, sour_cream, chili_sauce, + cutlet, dinosaur, sausage): + ingredients = database.available_ingredients() + # Соусы + assert ingredients[0].get_type() == hot_sauce.get_type() + assert ingredients[0].get_name() == hot_sauce.get_name() + assert ingredients[0].get_price() == hot_sauce.get_price() + assert ingredients[1].get_type() == sour_cream.get_type() + assert ingredients[1].get_name() == sour_cream.get_name() + assert ingredients[1].get_price() == sour_cream.get_price() + assert ingredients[2].get_type() == chili_sauce.get_type() + assert ingredients[2].get_name() == chili_sauce.get_name() + assert ingredients[2].get_price() == chili_sauce.get_price() + # Начинки + assert ingredients[3].get_type() == cutlet.get_type() + assert ingredients[3].get_name() == cutlet.get_name() + assert ingredients[3].get_price() == cutlet.get_price() + assert ingredients[4].get_type() == dinosaur.get_type() + assert ingredients[4].get_name() == dinosaur.get_name() + assert ingredients[4].get_price() == dinosaur.get_price() + assert ingredients[5].get_type() == sausage.get_type() + assert ingredients[5].get_name() == sausage.get_name() + assert ingredients[5].get_price() == sausage.get_price() + + def test_available_buns_returns_same_list(self, database): + buns1 = database.available_buns() + buns2 = database.available_buns() + assert buns1 is buns2 + + def test_available_ingredients_returns_same_list(self, database): + ing1 = database.available_ingredients() + ing2 = database.available_ingredients() + assert ing1 is ing2 \ No newline at end of file diff --git a/tests/test_ingredient.py b/tests/test_ingredient.py new file mode 100644 index 000000000..a6834c50d --- /dev/null +++ b/tests/test_ingredient.py @@ -0,0 +1,28 @@ +import pytest +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +class TestIngredient: + + @pytest.mark.parametrize("ingredient_type, name, price", [ + (INGREDIENT_TYPE_SAUCE, "hot sauce", 100), + (INGREDIENT_TYPE_SAUCE, "sour cream", 200), + (INGREDIENT_TYPE_FILLING, "cutlet", 100), + (INGREDIENT_TYPE_FILLING, "dinosaur", 200), + ("UNKNOWN", "strange", 0), + ]) + def test_ingredient_creation(self, ingredient_type, name, price): + ingredient = Ingredient(ingredient_type, name, price) + assert ingredient.type == ingredient_type + assert ingredient.name == name + assert ingredient.price == price + + def test_get_price(self, hot_sauce): + assert hot_sauce.get_price() == 100 + + def test_get_name(self, hot_sauce): + assert hot_sauce.get_name() == "hot sauce" + + def test_get_type(self, cutlet): + assert cutlet.get_type() == INGREDIENT_TYPE_FILLING \ No newline at end of file