diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0d7af75bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +.pytest_cache/ +*.egg-info/ +dist/ +build/ +.eggs/ + +# Virtual environment +venv/ +.venv/ +env/ + +# IDE +.idea/ +.vscode/ +*.iml + +# OS +.DS_Store +Thumbs.db + +# Logs and reports +*.log +reports/ +allure-results/ + +# Selenium / browser +chromedriver +geckodriver diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..22547294c --- /dev/null +++ b/conftest.py @@ -0,0 +1,54 @@ +import pytest +from selenium import webdriver +from unittest.mock import MagicMock + +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 + +from test_data import ( + BUN_PRICE, BUN_NAME, + SAUCE_PRICE, SAUCE_NAME, + FILLING_PRICE, FILLING_NAME +) + + +@pytest.fixture +def driver(): + driver = webdriver.Chrome() + yield driver + driver.quit() + + +@pytest.fixture +def mock_bun(): + bun = MagicMock(spec=Bun) + bun.get_name.return_value = BUN_NAME + bun.get_price.return_value = BUN_PRICE + return bun + + +@pytest.fixture +def mock_sauce(): + ingredient = MagicMock(spec=Ingredient) + ingredient.get_name.return_value = SAUCE_NAME + ingredient.get_price.return_value = SAUCE_PRICE + ingredient.get_type.return_value = INGREDIENT_TYPE_SAUCE + return ingredient + + +@pytest.fixture +def mock_filling(): + ingredient = MagicMock(spec=Ingredient) + ingredient.get_name.return_value = FILLING_NAME + ingredient.get_price.return_value = FILLING_PRICE + ingredient.get_type.return_value = INGREDIENT_TYPE_FILLING + return ingredient + + +@pytest.fixture +def burger_with_bun(mock_bun): + burger = Burger() + burger.set_buns(mock_bun) + return burger \ No newline at end of file diff --git a/ingredient_types.py b/ingredient_types.py deleted file mode 100644 index 34940ad5d..000000000 --- a/ingredient_types.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Перечисление с типами ингредиентов. -SAUCE – соус -FILLING – начинка -""" -INGREDIENT_TYPE_SAUCE = 'SAUCE' -INGREDIENT_TYPE_FILLING = 'FILLING' diff --git a/praktikum/__init__.py b/praktikum/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/bun.py b/praktikum/bun.py similarity index 58% rename from bun.py rename to praktikum/bun.py index 5504bc1f4..d0a826c2d 100644 --- a/bun.py +++ b/praktikum/bun.py @@ -1,9 +1,4 @@ class Bun: - """ - Модель булочки для бургера. - Булочке можно дать название и назначить цену. - """ - def __init__(self, name: str, price: float): self.name = name self.price = price diff --git a/burger.py b/praktikum/burger.py similarity index 77% rename from burger.py rename to praktikum/burger.py index 2b3b6a88b..4e28963fc 100644 --- a/burger.py +++ b/praktikum/burger.py @@ -1,17 +1,8 @@ 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] = [] @@ -30,19 +21,14 @@ def move_ingredient(self, index: int, new_index: int): 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/database.py b/praktikum/database.py similarity index 91% rename from database.py rename to praktikum/database.py index 4c75baf71..17a244243 100644 --- a/database.py +++ b/praktikum/database.py @@ -1,15 +1,9 @@ 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] = [] diff --git a/ingredient.py b/praktikum/ingredient.py similarity index 60% rename from ingredient.py rename to praktikum/ingredient.py index 0e50db8a2..0a8350eae 100644 --- a/ingredient.py +++ b/praktikum/ingredient.py @@ -1,10 +1,4 @@ class Ingredient: - """ - Модель ингредиента. - Ингредиент: начинка или соус. - У ингредиента есть тип (начинка или соус), название и цена. - """ - def __init__(self, ingredient_type: str, name: str, price: float): self.type = ingredient_type self.name = name diff --git a/praktikum/ingredient_types.py b/praktikum/ingredient_types.py new file mode 100644 index 000000000..84e983fff --- /dev/null +++ b/praktikum/ingredient_types.py @@ -0,0 +1,2 @@ +INGREDIENT_TYPE_SAUCE = 'SAUCE' +INGREDIENT_TYPE_FILLING = 'FILLING' diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..a635c5c03 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..78b9d1e15 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest==8.2.0 +pytest-cov==5.0.0 diff --git a/test_data.py b/test_data.py new file mode 100644 index 000000000..29d6b537f --- /dev/null +++ b/test_data.py @@ -0,0 +1,7 @@ +BUN_PRICE = 100 +SAUCE_PRICE = 50 +FILLING_PRICE = 100 + +BUN_NAME = 'black bun' +SAUCE_NAME = 'hot sauce' +FILLING_NAME = 'cutlet' \ 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..81cfa94b8 --- /dev/null +++ b/tests/test_bun.py @@ -0,0 +1,23 @@ +import pytest +from praktikum.bun import Bun + + +class TestBun: + + @pytest.mark.parametrize('name', [ + 'black bun', + 'white bun', + 'red bun', + ]) + def test_get_name(self, name): + bun = Bun(name, 100) + assert bun.get_name() == name + + @pytest.mark.parametrize('price', [ + 100, + 200.5, + 0, + ]) + def test_get_price(self, price): + bun = Bun('test bun', price) + assert bun.get_price() == 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..7a198aba6 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,118 @@ +import pytest +from unittest.mock import MagicMock + +from praktikum.burger import Burger +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE + +from test_data import ( + BUN_PRICE, BUN_NAME, + SAUCE_NAME +) + + +class TestBurgerSetBuns: + + def test_set_buns(self, mock_bun): + burger = Burger() + burger.set_buns(mock_bun) + assert burger.bun == mock_bun + + +class TestBurgerAddIngredient: + + def test_add_ingredient(self, burger_with_bun, mock_sauce): + burger_with_bun.add_ingredient(mock_sauce) + assert mock_sauce in burger_with_bun.ingredients + + def test_add_multiple_ingredients(self, burger_with_bun, mock_sauce, mock_filling): + burger_with_bun.add_ingredient(mock_sauce) + burger_with_bun.add_ingredient(mock_filling) + assert len(burger_with_bun.ingredients) == 2 + + +class TestBurgerRemoveIngredient: + + def test_remove_ingredient(self, burger_with_bun, mock_sauce, mock_filling): + burger_with_bun.add_ingredient(mock_sauce) + burger_with_bun.add_ingredient(mock_filling) + burger_with_bun.remove_ingredient(0) + assert mock_sauce not in burger_with_bun.ingredients + + def test_remove_ingredient_reduces_count(self, burger_with_bun, mock_sauce): + burger_with_bun.add_ingredient(mock_sauce) + burger_with_bun.remove_ingredient(0) + assert len(burger_with_bun.ingredients) == 0 + + +class TestBurgerMoveIngredient: + + def test_move_ingredient(self, burger_with_bun, mock_sauce, mock_filling): + burger_with_bun.add_ingredient(mock_sauce) + burger_with_bun.add_ingredient(mock_filling) + burger_with_bun.move_ingredient(1, 0) + assert burger_with_bun.ingredients[0] == mock_filling + assert burger_with_bun.ingredients[1] == mock_sauce + + +class TestBurgerGetPrice: + + def test_get_price_bun_only(self, burger_with_bun): + expected = BUN_PRICE * 2 + assert burger_with_bun.get_price() == expected + + def test_get_price_with_ingredients(self, burger_with_bun, mock_sauce, mock_filling): + burger_with_bun.add_ingredient(mock_sauce) + burger_with_bun.add_ingredient(mock_filling) + expected = BUN_PRICE * 2 + mock_sauce.get_price() + mock_filling.get_price() + assert burger_with_bun.get_price() == expected + + @pytest.mark.parametrize('bun_price, ingredient_prices, expected', [ + (100, [], 200), + (100, [50], 250), + (200, [100, 150], 650), + ]) + def test_get_price_parametrized(self, bun_price, ingredient_prices, expected): + bun = MagicMock(spec=Bun) + bun.get_price.return_value = bun_price + bun.get_name.return_value = 'bun' + + burger = Burger() + burger.set_buns(bun) + + for price in ingredient_prices: + ingredient = MagicMock(spec=Ingredient) + ingredient.get_price.return_value = price + ingredient.get_name.return_value = 'ing' + ingredient.get_type.return_value = INGREDIENT_TYPE_SAUCE + burger.add_ingredient(ingredient) + + assert burger.get_price() == expected + + +class TestBurgerGetReceipt: + + def test_get_receipt_contains_bun_name(self, burger_with_bun): + assert BUN_NAME in burger_with_bun.get_receipt() + + def test_get_receipt_contains_price(self, burger_with_bun): + expected_price = BUN_PRICE * 2 + assert f'Price: {expected_price}' in burger_with_bun.get_receipt() + + def test_get_receipt_contains_ingredient_name(self, burger_with_bun, mock_sauce): + burger_with_bun.add_ingredient(mock_sauce) + assert SAUCE_NAME in burger_with_bun.get_receipt() + + def test_get_receipt_contains_ingredient_type(self, burger_with_bun, mock_sauce): + burger_with_bun.add_ingredient(mock_sauce) + assert mock_sauce.get_type().lower() in burger_with_bun.get_receipt() + + def test_get_receipt_format(self, burger_with_bun, mock_sauce): + burger_with_bun.add_ingredient(mock_sauce) + receipt = burger_with_bun.get_receipt() + + lines = receipt.split('\n') + + assert lines[0] == f'(==== {BUN_NAME} ====)' + assert f'(==== {BUN_NAME} ====)' in lines \ No newline at end of file diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 000000000..8c52e4565 --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,32 @@ +from praktikum.database import Database +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient + + +class TestDatabase: + + def test_available_buns_returns_list(self): + db = Database() + assert isinstance(db.available_buns(), list) + + def test_available_buns_not_empty(self): + db = Database() + assert len(db.available_buns()) > 0 + + def test_available_buns_contains_bun_instances(self): + db = Database() + for bun in db.available_buns(): + assert isinstance(bun, Bun) + + def test_available_ingredients_returns_list(self): + db = Database() + assert isinstance(db.available_ingredients(), list) + + def test_available_ingredients_not_empty(self): + db = Database() + assert len(db.available_ingredients()) > 0 + + def test_available_ingredients_contains_ingredient_instances(self): + db = Database() + for ingredient in db.available_ingredients(): + assert isinstance(ingredient, Ingredient) diff --git a/tests/test_ingredient.py b/tests/test_ingredient.py new file mode 100644 index 000000000..821ca9aa2 --- /dev/null +++ b/tests/test_ingredient.py @@ -0,0 +1,35 @@ +import pytest +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import ( + INGREDIENT_TYPE_SAUCE, + INGREDIENT_TYPE_FILLING +) + + +class TestIngredient: + + @pytest.mark.parametrize('name', [ + 'hot sauce', + 'cutlet', + 'sour cream', + ]) + def test_get_name(self, name): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, name, 100) + assert ingredient.get_name() == name + + @pytest.mark.parametrize('price', [ + 100, + 200, + 50.5, + ]) + def test_get_price(self, price): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, 'test', price) + assert ingredient.get_price() == price + + @pytest.mark.parametrize('ingredient_type', [ + INGREDIENT_TYPE_SAUCE, + INGREDIENT_TYPE_FILLING, + ]) + def test_get_type(self, ingredient_type): + ingredient = Ingredient(ingredient_type, 'test', 100) + assert ingredient.get_type() == ingredient_type \ No newline at end of file