diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e31c29186 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Virtual environments +venv/ +.env/ +.venv/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# pytest +.pytest_cache/ +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# OS +.DS_Store +Thumbs.db +.coverage \ No newline at end of file diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..c8bfb7e65 --- /dev/null +++ b/conftest.py @@ -0,0 +1,48 @@ +import pytest +from unittest.mock import Mock +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING + + +@pytest.fixture +def mock_bun(): + mock = Mock(spec=Bun) + mock.get_name.return_value = "Краторная булка N-200i" + mock.get_price.return_value = 1255 + return mock + + +@pytest.fixture +def mock_ingredient(): + mock = Mock(spec=Ingredient) + mock.get_name.return_value = "Тестовый ингредиент" + mock.get_price.return_value = 100 + mock.get_type.return_value = INGREDIENT_TYPE_FILLING + return mock + + +@pytest.fixture +def sample_burger(): + from praktikum.burger import Burger + burger = Burger() + + bun = Mock(spec=Bun) + bun.get_price.return_value = 1255 + bun.get_name.return_value = "Краторная булка N-200i" + + ingredient1 = Mock(spec=Ingredient) + ingredient1.get_price.return_value = 3000 + ingredient1.get_name.return_value = "Говяжий метеорит (отбивная)" + ingredient1.get_type.return_value = INGREDIENT_TYPE_FILLING + + ingredient2 = Mock(spec=Ingredient) + ingredient2.get_price.return_value = 90 + ingredient2.get_name.return_value = "Соус Spicy-X" + ingredient2.get_type.return_value = INGREDIENT_TYPE_SAUCE + + burger.set_buns(bun) + burger.add_ingredient(ingredient1) + burger.add_ingredient(ingredient2) + + return burger \ 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/requirements.txt b/requirements.txt new file mode 100644 index 000000000..0b5144900 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +certifi==2025.8.3 +coverage==7.10.5 +iniconfig==2.1.0 +packaging==25.0 +pluggy==1.6.0 +pytest==8.4.1 +pytest-cov==6.2.1 \ 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/test_bun.py b/tests/test_bun.py new file mode 100644 index 000000000..91b5de0da --- /dev/null +++ b/tests/test_bun.py @@ -0,0 +1,51 @@ +import pytest +from praktikum.bun import Bun + + +class TestBun: + + def test_get_name_returns_correct_name(self): + bun = Bun("Краторная булка N-200i", 1255) + actual_name = bun.get_name() + assert actual_name == "Краторная булка N-200i" + + def test_get_price_returns_correct_price(self): + bun = Bun("Флюоресцентная булка R2-D3", 988) + actual_price = bun.get_price() + assert actual_price == 988 + + @pytest.mark.parametrize("name", [ + "Краторная булка N-200i", + "Флюоресцентная булка R2-D3", + "Специальная космическая булка", + "", + "Очень длинное название булочки для бургера" + ]) + def test_bun_initialization_with_different_names(self, name): + bun = Bun(name, 100) + actual_name = bun.get_name() + assert actual_name == name + + @pytest.mark.parametrize("price", [ + 1255, + 988, + 1500, + 0, + 999.99 + ]) + def test_bun_initialization_with_different_prices(self, price): + bun = Bun("Тестовая булка", price) + actual_price = bun.get_price() + assert actual_price == price + + def test_bun_initialization_and_retrieval(self): + expected_name = "Тестовая булка" + expected_price = 1000 + + bun = Bun(expected_name, expected_price) + + actual_name = bun.get_name() + actual_price = bun.get_price() + + assert actual_name == expected_name + assert actual_price == expected_price \ No newline at end of file diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..3c839e9ee --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,228 @@ +import pytest +from unittest.mock import Mock +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 + + +class TestBurger: + + def setup_method(self): + self.burger = Burger() + self.mock_bun = Mock(spec=Bun) + self.mock_ingredient1 = Mock(spec=Ingredient) + self.mock_ingredient2 = Mock(spec=Ingredient) + + def test_set_buns_sets_bun_correctly(self): + self.burger.set_buns(self.mock_bun) + assert self.burger.bun == self.mock_bun + + def test_add_ingredient_adds_ingredient_to_list(self): + initial_count = len(self.burger.ingredients) + self.burger.add_ingredient(self.mock_ingredient1) + + assert len(self.burger.ingredients) == initial_count + 1 + assert self.burger.ingredients[0] == self.mock_ingredient1 + + def test_remove_ingredient_removes_ingredient_by_index(self): + self.burger.add_ingredient(self.mock_ingredient1) + self.burger.add_ingredient(self.mock_ingredient2) + + initial_count = len(self.burger.ingredients) + self.burger.remove_ingredient(0) + + assert len(self.burger.ingredients) == initial_count - 1 + assert self.burger.ingredients[0] == self.mock_ingredient2 + + def test_remove_ingredient_raises_index_error_for_invalid_index(self): + self.burger.add_ingredient(self.mock_ingredient1) + initial_count = len(self.burger.ingredients) + + with pytest.raises(IndexError): + self.burger.remove_ingredient(5) + + assert len(self.burger.ingredients) == initial_count + + def test_move_ingredient_changes_ingredient_position(self): + self.burger.add_ingredient(self.mock_ingredient1) + self.burger.add_ingredient(self.mock_ingredient2) + + self.burger.move_ingredient(0, 1) + + assert self.burger.ingredients[0] == self.mock_ingredient2 + assert self.burger.ingredients[1] == self.mock_ingredient1 + + def test_move_ingredient_to_same_position_does_nothing(self): + self.burger.add_ingredient(self.mock_ingredient1) + self.burger.add_ingredient(self.mock_ingredient2) + + original_order = self.burger.ingredients.copy() + self.burger.move_ingredient(0, 0) + + assert self.burger.ingredients == original_order + + def test_move_ingredient_raises_index_error_for_invalid_index(self): + self.burger.add_ingredient(self.mock_ingredient1) + initial_count = len(self.burger.ingredients) + + with pytest.raises(IndexError): + self.burger.move_ingredient(5, 0) + + assert len(self.burger.ingredients) == initial_count + + def test_get_price_calculates_total_with_bun_and_ingredients(self): + self.mock_bun.get_price.return_value = 1255 + self.mock_ingredient1.get_price.return_value = 3000 + self.mock_ingredient2.get_price.return_value = 4400 + + self.burger.set_buns(self.mock_bun) + self.burger.add_ingredient(self.mock_ingredient1) + self.burger.add_ingredient(self.mock_ingredient2) + + expected_price = 1255 * 2 + 3000 + 4400 + actual_price = self.burger.get_price() + + assert actual_price == expected_price + + def test_get_price_calculates_total_with_only_bun(self): + self.mock_bun.get_price.return_value = 988 + + self.burger.set_buns(self.mock_bun) + actual_price = self.burger.get_price() + + assert actual_price == 1976 + + def test_get_price_raises_error_without_bun(self): + self.burger.add_ingredient(self.mock_ingredient1) + self.mock_ingredient1.get_price.return_value = 424 + + with pytest.raises(AttributeError): + self.burger.get_price() + + def test_get_receipt_returns_correct_format_with_ingredients(self): + self.mock_bun.get_name.return_value = "Краторная булка N-200i" + self.mock_bun.get_price.return_value = 1255 + + mock_sauce = Mock(spec=Ingredient) + mock_sauce.get_type.return_value = INGREDIENT_TYPE_SAUCE + mock_sauce.get_name.return_value = "Соус Spicy-X" + mock_sauce.get_price.return_value = 90 + + mock_filling = Mock(spec=Ingredient) + mock_filling.get_type.return_value = INGREDIENT_TYPE_FILLING + mock_filling.get_name.return_value = "Говяжий метеорит (отбивная)" + mock_filling.get_price.return_value = 3000 + + self.burger.set_buns(self.mock_bun) + self.burger.add_ingredient(mock_sauce) + self.burger.add_ingredient(mock_filling) + + receipt = self.burger.get_receipt() + + expected_lines = [ + "(==== Краторная булка N-200i ====)", + "= sauce Соус Spicy-X =", + "= filling Говяжий метеорит (отбивная) =", + "(==== Краторная булка N-200i ====)", + "", + "Price: 5600" + ] + expected_receipt = "\n".join(expected_lines) + + assert receipt == expected_receipt + + def test_get_receipt_returns_correct_format_with_only_bun(self): + self.mock_bun.get_name.return_value = "Флюоресцентная булка R2-D3" + self.mock_bun.get_price.return_value = 988 + + self.burger.set_buns(self.mock_bun) + receipt = self.burger.get_receipt() + + expected_lines = [ + "(==== Флюоресцентная булка R2-D3 ====)", + "(==== Флюоресцентная булка R2-D3 ====)", + "", + "Price: 1976" + ] + expected_receipt = "\n".join(expected_lines) + + assert receipt == expected_receipt + + def test_get_receipt_raises_error_without_bun(self): + mock_ingredient = Mock(spec=Ingredient) + mock_ingredient.get_type.return_value = INGREDIENT_TYPE_SAUCE + mock_ingredient.get_name.return_value = "Соус традиционный галактический" + mock_ingredient.get_price.return_value = 15 + + self.burger.add_ingredient(mock_ingredient) + + with pytest.raises(AttributeError): + self.burger.get_receipt() + + @pytest.mark.parametrize("bun_price,ingredient_prices,expected_total", [ + (1255, [3000, 4400], 9910), + (988, [], 1976), + (200, [100], 500), + ]) + def test_get_price_with_different_combinations(self, bun_price, ingredient_prices, expected_total): + self.mock_bun.get_price.return_value = bun_price + self.burger.set_buns(self.mock_bun) + + for price in ingredient_prices: + mock_ingredient = Mock(spec=Ingredient) + mock_ingredient.get_price.return_value = price + self.burger.add_ingredient(mock_ingredient) + + actual_price = self.burger.get_price() + assert actual_price == expected_total + + def test_get_receipt_includes_all_ingredients(self): + self.mock_bun.get_name.return_value = "Краторная булка N-200i" + self.mock_bun.get_price.return_value = 1255 + + mock_ingredients = [ + ("Соус Spicy-X", INGREDIENT_TYPE_SAUCE, 90), + ("Говяжий метеорит (отбивная)", INGREDIENT_TYPE_FILLING, 3000), + ("Биокотлета из марсианской Магнолии", INGREDIENT_TYPE_FILLING, 424), + ("Хрустящие минеральные кольца", INGREDIENT_TYPE_FILLING, 300) + ] + + self.burger.set_buns(self.mock_bun) + + for name, ing_type, price in mock_ingredients: + mock_ingredient = Mock(spec=Ingredient) + mock_ingredient.get_name.return_value = name + mock_ingredient.get_type.return_value = ing_type + mock_ingredient.get_price.return_value = price + self.burger.add_ingredient(mock_ingredient) + + receipt = self.burger.get_receipt() + + assert "Краторная булка N-200i" in receipt + assert "Соус Spicy-X" in receipt + assert "Говяжий метеорит (отбивная)" in receipt + assert "Биокотлета из марсианской Магнолии" in receipt + assert "Хрустящие минеральные кольца" in receipt + assert "Price: 6324" in receipt + + def test_get_price_with_zero_bun_price(self): + self.mock_bun.get_price.return_value = 0 + self.mock_ingredient1.get_price.return_value = 424 + self.mock_ingredient2.get_price.return_value = 762 + + self.burger.set_buns(self.mock_bun) + self.burger.add_ingredient(self.mock_ingredient1) + self.burger.add_ingredient(self.mock_ingredient2) + + actual_price = self.burger.get_price() + assert actual_price == 1186 + + def test_add_multiple_ingredients_increases_count(self): + initial_count = len(self.burger.ingredients) + self.burger.add_ingredient(self.mock_ingredient1) + self.burger.add_ingredient(self.mock_ingredient2) + + assert len(self.burger.ingredients) == initial_count + 2 + assert self.burger.ingredients[0] == self.mock_ingredient1 + assert self.burger.ingredients[1] == self.mock_ingredient2 \ No newline at end of file diff --git a/tests/test_ingredient.py b/tests/test_ingredient.py new file mode 100644 index 000000000..9d5a46b00 --- /dev/null +++ b/tests/test_ingredient.py @@ -0,0 +1,53 @@ +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, "Соус Spicy-X", 90), + (INGREDIENT_TYPE_FILLING, "Говяжий метеорит (отбивная)", 3000), + (INGREDIENT_TYPE_SAUCE, "Соус фирменный Space Sauce", 80), + (INGREDIENT_TYPE_FILLING, "Биокотлета из марсианской Магнолии", 424) + ]) + def test_ingredient_initialization_with_different_values(self, ingredient_type, name, price): + ingredient = Ingredient(ingredient_type, name, price) + + actual_type = ingredient.get_type() + actual_name = ingredient.get_name() + actual_price = ingredient.get_price() + + assert actual_type == ingredient_type + assert actual_name == name + assert actual_price == price + + def test_get_price_returns_correct_price(self): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, "Соус традиционный галактический", 15) + actual_price = ingredient.get_price() + assert actual_price == 15 + + def test_get_name_returns_correct_name(self): + ingredient = Ingredient(INGREDIENT_TYPE_FILLING, "Мясо бессмертных моллюсков Protostomia", 1337) + actual_name = ingredient.get_name() + assert actual_name == "Мясо бессмертных моллюсков Protostomia" + + def test_get_type_returns_correct_type(self): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, "Соус с шипами Антарианского плоскоходца", 88) + actual_type = ingredient.get_type() + assert actual_type == INGREDIENT_TYPE_SAUCE + + @pytest.mark.parametrize("ingredient_type", [ + INGREDIENT_TYPE_SAUCE, + INGREDIENT_TYPE_FILLING + ]) + def test_ingredient_with_different_types(self, ingredient_type): + ingredient = Ingredient(ingredient_type, "Тестовый ингредиент", 100) + actual_type = ingredient.get_type() + assert actual_type == ingredient_type + + @pytest.mark.parametrize("price", [0, 100, 999.99]) + def test_ingredient_with_different_prices(self, price): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, "Тестовый соус", price) + actual_price = ingredient.get_price() + assert actual_price == price \ No newline at end of file