diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8e9fbe806 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# 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/ +.coverage +# htmlcov/ - отчет должен быть в репозитории +.tox/ + +# Virtual environments +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + diff --git a/README.md b/README.md index 272081708..8980bed30 100644 --- a/README.md +++ b/README.md @@ -1,24 +1 @@ -## Задание 1: Юнит-тесты - -### Автотесты для проверки программы, которая помогает заказать бургер в Stellar Burgers - -### Реализованные сценарии - -Созданы юнит-тесты, покрывающие классы `Bun`, `Burger`, `Ingredient`, `Database` - -Процент покрытия 100% (отчет: `htmlcov/index.html`) - -### Структура проекта - -- `praktikum` - пакет, содержащий код программы -- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. - -### Запуск автотестов - -**Установка зависимостей** - -> `$ pip install -r requirements.txt` - -**Запуск автотестов и создание HTML-отчета о покрытии** - -> `$ pytest --cov=praktikum --cov-report=html` +# Task_1 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.py b/praktikum.py index ec522fa6d..cfe791e83 100644 --- a/praktikum.py +++ b/praktikum.py @@ -6,6 +6,7 @@ from praktikum.ingredient import Ingredient +def main(): def main(): # Инициализируем базу данных database: Database = Database() 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 59% rename from bun.py rename to praktikum/bun.py index 5504bc1f4..ad77a4c82 100644 --- a/bun.py +++ b/praktikum/bun.py @@ -1,8 +1,6 @@ +# Модель булочки для бургера. +# Булочке можно дать название и назначить цену. class Bun: - """ - Модель булочки для бургера. - Булочке можно дать название и назначить цену. - """ def __init__(self, name: str, price: float): self.name = name diff --git a/burger.py b/praktikum/burger.py similarity index 78% rename from burger.py rename to praktikum/burger.py index 2b3b6a88b..eba6ad6f2 100644 --- a/burger.py +++ b/praktikum/burger.py @@ -4,13 +4,11 @@ from praktikum.ingredient import Ingredient +# Модель бургера. +# Бургер состоит из булочек и ингредиентов (начинка или соус). +# Ингредиенты можно перемещать и удалять. +# Можно распечать чек с информацией о бургере. class Burger: - """ - Модель бургера. - Бургер состоит из булочек и ингредиентов (начинка или соус). - Ингредиенты можно перемещать и удалять. - Можно распечать чек с информацией о бургере. - """ def __init__(self): self.bun = None diff --git a/database.py b/praktikum/database.py similarity index 92% rename from database.py rename to praktikum/database.py index 4c75baf71..0a4a5c73a 100644 --- a/database.py +++ b/praktikum/database.py @@ -5,10 +5,8 @@ from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING +# Класс с методами по работе с базой данных. class Database: - """ - Класс с методами по работе с базой данных. - """ def __init__(self): self.buns: List[Bun] = [] diff --git a/ingredient.py b/praktikum/ingredient.py similarity index 61% rename from ingredient.py rename to praktikum/ingredient.py index 0e50db8a2..a0ca649ad 100644 --- a/ingredient.py +++ b/praktikum/ingredient.py @@ -1,9 +1,7 @@ +# Модель ингредиента. +# Ингредиент: начинка или соус. +# У ингредиента есть тип (начинка или соус), название и цена. class Ingredient: - """ - Модель ингредиента. - Ингредиент: начинка или соус. - У ингредиента есть тип (начинка или соус), название и цена. - """ def __init__(self, ingredient_type: str, name: str, price: float): self.type = ingredient_type diff --git a/praktikum/ingredient_types.py b/praktikum/ingredient_types.py new file mode 100644 index 000000000..6705b64bf --- /dev/null +++ b/praktikum/ingredient_types.py @@ -0,0 +1,5 @@ +# Перечисление с типами ингредиентов. +# SAUCE – соус +# FILLING – начинка +INGREDIENT_TYPE_SAUCE = 'SAUCE' +INGREDIENT_TYPE_FILLING = 'FILLING' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..08af36a00 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pytest==7.4.3 +pytest-cov==4.1.0 + 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..1e0fc5d7a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,22 @@ +# Фикстуры для тестов. + +import pytest +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from tests.test_constants import ( + BUN_BLACK, PRICE_100, + INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE +) + + +@pytest.fixture +def ingredient(): + # Фикстура для создания ингредиента. + return Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + + +@pytest.fixture +def bun(): + # Фикстура для создания булочки. + return Bun(BUN_BLACK, PRICE_100) + diff --git a/tests/test_bun.py b/tests/test_bun.py new file mode 100644 index 000000000..9de26b3d7 --- /dev/null +++ b/tests/test_bun.py @@ -0,0 +1,27 @@ +import pytest +from praktikum.bun import Bun +from tests.test_constants import ( + TEST_BUNS, BUN_TEST, PRICE_150 +) + + +# Тесты для класса Bun. +class TestBun: + + @pytest.mark.parametrize("name,price", TEST_BUNS) + def test_bun_get_name(self, name, price): + # Проверка получения названия булочки. + bun = Bun(name, price) + assert bun.get_name() == name + + @pytest.mark.parametrize("name,price", TEST_BUNS) + def test_bun_get_price(self, name, price): + # Проверка получения цены булочки. + bun = Bun(name, price) + assert bun.get_price() == price + + def test_bun_initialization(self): + # Проверка инициализации булочки. + bun = Bun(BUN_TEST, PRICE_150) + assert bun.name == BUN_TEST + assert bun.price == PRICE_150 diff --git a/tests/test_burger.py b/tests/test_burger.py new file mode 100644 index 000000000..fb19f14d1 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,155 @@ +import pytest +from unittest.mock import Mock + +from praktikum.burger import Burger +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from tests.test_constants import ( + BUN_BLACK, BUN_WHITE, BUN_RED, BUN_TEST, + PRICE_50, PRICE_100, PRICE_150, PRICE_200, PRICE_500, PRICE_400, PRICE_75, + INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING, + INGREDIENT_HOT_SAUCE, INGREDIENT_SOUR_CREAM, INGREDIENT_CUTLET +) + + +class TestBurger: + # Тесты для класса Burger. + + def test_set_buns(self, bun): + # Проверка установки булочек. + burger = Burger() + burger.set_buns(bun) + assert burger.bun == bun + + def test_add_ingredient(self, ingredient): + # Проверка добавления ингредиента. + burger = Burger() + burger.add_ingredient(ingredient) + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == ingredient + + def test_add_multiple_ingredients(self): + # Проверка добавления нескольких ингредиентов. + burger = Burger() + ingredient1 = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + ingredient2 = Ingredient(INGREDIENT_TYPE_FILLING, INGREDIENT_CUTLET, PRICE_100) + burger.add_ingredient(ingredient1) + burger.add_ingredient(ingredient2) + assert len(burger.ingredients) == 2 + + def test_remove_ingredient(self): + # Проверка удаления ингредиента. + burger = Burger() + ingredient1 = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + ingredient2 = Ingredient(INGREDIENT_TYPE_FILLING, INGREDIENT_CUTLET, PRICE_100) + burger.add_ingredient(ingredient1) + burger.add_ingredient(ingredient2) + burger.remove_ingredient(0) + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == ingredient2 + + def test_move_ingredient(self): + # Проверка перемещения ингредиента. + burger = Burger() + ingredient1 = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + ingredient2 = Ingredient(INGREDIENT_TYPE_FILLING, INGREDIENT_CUTLET, PRICE_100) + ingredient3 = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_SOUR_CREAM, PRICE_200) + burger.add_ingredient(ingredient1) + burger.add_ingredient(ingredient2) + burger.add_ingredient(ingredient3) + burger.move_ingredient(0, 2) + assert burger.ingredients[2] == ingredient1 + + @pytest.mark.parametrize("bun_name,bun_price,ingredient_count,expected_price", [ + (BUN_BLACK, PRICE_100, 0, PRICE_200), + (BUN_WHITE, PRICE_50, 1, PRICE_200), + (BUN_RED, PRICE_100, 3, PRICE_500), + ]) + def test_get_price(self, bun_name, bun_price, ingredient_count, expected_price): + # Проверка расчета цены бургера. + burger = Burger() + bun = Bun(bun_name, bun_price) + burger.set_buns(bun) + + # Добавляем ингредиенты с фиксированной ценой + for i in range(ingredient_count): + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + burger.add_ingredient(ingredient) + + assert burger.get_price() == expected_price + + def test_get_receipt(self, bun): + # Проверка получения чека. + burger = Burger() + burger.set_buns(bun) + ingredient1 = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + ingredient2 = Ingredient(INGREDIENT_TYPE_FILLING, INGREDIENT_CUTLET, PRICE_100) + burger.add_ingredient(ingredient1) + burger.add_ingredient(ingredient2) + + receipt = burger.get_receipt() + assert BUN_BLACK in receipt + assert INGREDIENT_HOT_SAUCE in receipt + assert INGREDIENT_CUTLET in receipt + assert f"Price: {PRICE_400}" in receipt + + def test_get_receipt_empty_ingredients(self, bun): + # Проверка получения чека без ингредиентов. + burger = Burger() + burger.set_buns(bun) + + receipt = burger.get_receipt() + assert BUN_BLACK in receipt + assert f"Price: {PRICE_200}" in receipt + + def test_get_price_with_mocked_bun(self): + # Проверка расчета цены с использованием мока для булочки. + burger = Burger() + mock_bun = Mock() + mock_bun.get_price.return_value = PRICE_50 + burger.set_buns(mock_bun) + + price = burger.get_price() + assert price == PRICE_100 # Две булочки + mock_bun.get_price.assert_called() + + def test_get_price_with_mocked_ingredients(self): + # Проверка расчета цены с использованием моков для ингредиентов. + burger = Burger() + bun = Bun(BUN_TEST, PRICE_100) + burger.set_buns(bun) + + mock_ingredient = Mock() + mock_ingredient.get_price.return_value = PRICE_75 + burger.add_ingredient(mock_ingredient) + burger.add_ingredient(mock_ingredient) + + price = burger.get_price() + expected_price = PRICE_200 + PRICE_150 # 200 за булочки (100*2) + 150 за ингредиенты (75*2) + assert price == expected_price + assert mock_ingredient.get_price.call_count == 2 + + def test_remove_ingredient_with_invalid_index(self): + # Проверка удаления ингредиента с недопустимым индексом. + burger = Burger() + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + burger.add_ingredient(ingredient) + + # Попытка удалить несуществующий индекс должна вызвать IndexError + with pytest.raises(IndexError) as excinfo: + burger.remove_ingredient(1) + + assert isinstance(excinfo.value, IndexError) + + def test_move_ingredient_with_invalid_indices(self): + # Проверка перемещения ингредиента с недопустимыми индексами. + burger = Burger() + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100) + burger.add_ingredient(ingredient) + + # Попытка переместить несуществующий индекс должна вызвать IndexError + with pytest.raises(IndexError) as excinfo: + burger.move_ingredient(1, 0) + + assert isinstance(excinfo.value, IndexError) + diff --git a/tests/test_constants.py b/tests/test_constants.py new file mode 100644 index 000000000..4929e115c --- /dev/null +++ b/tests/test_constants.py @@ -0,0 +1,50 @@ +# Общие константы для тестов. +# Содержит все тестовые данные, используемые в различных тестах. + +# Константы для типов ингредиентов +INGREDIENT_TYPE_SAUCE = "SAUCE" +INGREDIENT_TYPE_FILLING = "FILLING" + +# Константы для названий булочек +BUN_BLACK = "black bun" +BUN_WHITE = "white bun" +BUN_RED = "red bun" +BUN_SESAME = "sesame bun" +BUN_TEST = "test bun" + +# Константы для названий ингредиентов +INGREDIENT_HOT_SAUCE = "hot sauce" +INGREDIENT_SOUR_CREAM = "sour cream" +INGREDIENT_CHILI_SAUCE = "chili sauce" +INGREDIENT_CUTLET = "cutlet" +INGREDIENT_DINOSAUR = "dinosaur" +INGREDIENT_SAUSAGE = "sausage" +INGREDIENT_TEST_SAUCE = "test sauce" + +# Константы для цен +PRICE_50 = 50 +PRICE_100 = 100 +PRICE_150 = 150 +PRICE_200 = 200 +PRICE_300 = 300 +PRICE_500 = 500 +PRICE_350 = 350 +PRICE_400 = 400 +PRICE_75 = 75 + +# Тестовые данные для параметризации булочек +TEST_BUNS = [ + (BUN_BLACK, PRICE_100), + (BUN_WHITE, PRICE_200), + (BUN_RED, PRICE_300), + (BUN_SESAME, PRICE_50), +] + +# Тестовые данные для параметризации ингредиентов +TEST_INGREDIENTS = [ + (INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE, PRICE_100), + (INGREDIENT_TYPE_SAUCE, INGREDIENT_SOUR_CREAM, PRICE_200), + (INGREDIENT_TYPE_FILLING, INGREDIENT_CUTLET, PRICE_100), + (INGREDIENT_TYPE_FILLING, INGREDIENT_DINOSAUR, PRICE_200), +] + diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 000000000..85ec0f905 --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,101 @@ +import pytest + +from praktikum.database import Database +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.ingredient_types import INGREDIENT_TYPE_SAUCE, INGREDIENT_TYPE_FILLING +from tests.test_constants import ( + BUN_BLACK, BUN_WHITE, BUN_RED, + PRICE_100, PRICE_200, PRICE_300, + INGREDIENT_HOT_SAUCE, INGREDIENT_SOUR_CREAM, INGREDIENT_CHILI_SAUCE, + INGREDIENT_CUTLET, INGREDIENT_DINOSAUR, INGREDIENT_SAUSAGE +) + + +class TestDatabase: + # Тесты для класса Database. + + def test_database_initialization(self): + # Проверка инициализации базы данных. + database = Database() + assert len(database.buns) == 3 + assert len(database.ingredients) == 6 + + def test_available_buns(self): + # Проверка получения доступных булочек. + database = Database() + buns = database.available_buns() + assert len(buns) == 3 + assert isinstance(buns[0], Bun) + assert buns[0].get_name() == BUN_BLACK + assert buns[1].get_name() == BUN_WHITE + assert buns[2].get_name() == BUN_RED + + def test_available_ingredients(self): + # Проверка получения доступных ингредиентов. + database = Database() + ingredients = database.available_ingredients() + assert len(ingredients) == 6 + assert isinstance(ingredients[0], Ingredient) + # Проверка соусов + assert ingredients[0].get_type() == INGREDIENT_TYPE_SAUCE + assert ingredients[0].get_name() == INGREDIENT_HOT_SAUCE + assert ingredients[1].get_type() == INGREDIENT_TYPE_SAUCE + assert ingredients[1].get_name() == INGREDIENT_SOUR_CREAM + assert ingredients[2].get_type() == INGREDIENT_TYPE_SAUCE + assert ingredients[2].get_name() == INGREDIENT_CHILI_SAUCE + # Проверка начинок + assert ingredients[3].get_type() == INGREDIENT_TYPE_FILLING + assert ingredients[3].get_name() == INGREDIENT_CUTLET + assert ingredients[4].get_type() == INGREDIENT_TYPE_FILLING + assert ingredients[4].get_name() == INGREDIENT_DINOSAUR + assert ingredients[5].get_type() == INGREDIENT_TYPE_FILLING + assert ingredients[5].get_name() == INGREDIENT_SAUSAGE + + def test_bun_prices(self): + # Проверка цен булочек. + database = Database() + buns = database.available_buns() + assert buns[0].get_price() == PRICE_100 + assert buns[1].get_price() == PRICE_200 + assert buns[2].get_price() == PRICE_300 + + def test_ingredient_prices(self): + # Проверка цен ингредиентов. + database = Database() + ingredients = database.available_ingredients() + assert ingredients[0].get_price() == PRICE_100 + assert ingredients[1].get_price() == PRICE_200 + assert ingredients[2].get_price() == PRICE_300 + assert ingredients[3].get_price() == PRICE_100 + assert ingredients[4].get_price() == PRICE_200 + assert ingredients[5].get_price() == PRICE_300 + + @pytest.mark.parametrize("bun_index,expected_name,expected_price", [ + (0, BUN_BLACK, PRICE_100), + (1, BUN_WHITE, PRICE_200), + (2, BUN_RED, PRICE_300), + ]) + def test_buns_parametrized(self, bun_index, expected_name, expected_price): + # Параметризованная проверка булочек. + database = Database() + buns = database.available_buns() + assert isinstance(buns[bun_index], Bun) + assert buns[bun_index].get_name() == expected_name + assert buns[bun_index].get_price() == expected_price + + @pytest.mark.parametrize("ingredient_index,expected_type,expected_name", [ + (0, INGREDIENT_TYPE_SAUCE, INGREDIENT_HOT_SAUCE), + (1, INGREDIENT_TYPE_SAUCE, INGREDIENT_SOUR_CREAM), + (2, INGREDIENT_TYPE_SAUCE, INGREDIENT_CHILI_SAUCE), + (3, INGREDIENT_TYPE_FILLING, INGREDIENT_CUTLET), + (4, INGREDIENT_TYPE_FILLING, INGREDIENT_DINOSAUR), + (5, INGREDIENT_TYPE_FILLING, INGREDIENT_SAUSAGE), + ]) + def test_ingredients_parametrized(self, ingredient_index, expected_type, expected_name): + # Параметризованная проверка ингредиентов. + database = Database() + ingredients = database.available_ingredients() + assert isinstance(ingredients[ingredient_index], Ingredient) + assert ingredients[ingredient_index].get_type() == expected_type + assert ingredients[ingredient_index].get_name() == expected_name diff --git a/tests/test_ingredient.py b/tests/test_ingredient.py new file mode 100644 index 000000000..8107b4629 --- /dev/null +++ b/tests/test_ingredient.py @@ -0,0 +1,34 @@ +import pytest +from praktikum.ingredient import Ingredient +from tests.test_constants import ( + TEST_INGREDIENTS, INGREDIENT_TYPE_SAUCE, INGREDIENT_TEST_SAUCE, PRICE_150 +) + + +# Тесты для класса Ingredient. +class TestIngredient: + + @pytest.mark.parametrize("ingredient_type,name,price", TEST_INGREDIENTS) + def test_ingredient_get_name(self, ingredient_type, name, price): + # Проверка получения названия ингредиента. + ingredient = Ingredient(ingredient_type, name, price) + assert ingredient.get_name() == name + + @pytest.mark.parametrize("ingredient_type,name,price", TEST_INGREDIENTS) + def test_ingredient_get_price(self, ingredient_type, name, price): + # Проверка получения цены ингредиента. + ingredient = Ingredient(ingredient_type, name, price) + assert ingredient.get_price() == price + + @pytest.mark.parametrize("ingredient_type,name,price", TEST_INGREDIENTS) + def test_ingredient_get_type(self, ingredient_type, name, price): + # Проверка получения типа ингредиента. + ingredient = Ingredient(ingredient_type, name, price) + assert ingredient.get_type() == ingredient_type + + def test_ingredient_initialization(self): + # Проверка инициализации ингредиента. + ingredient = Ingredient(INGREDIENT_TYPE_SAUCE, INGREDIENT_TEST_SAUCE, PRICE_150) + assert ingredient.type == INGREDIENT_TYPE_SAUCE + assert ingredient.name == INGREDIENT_TEST_SAUCE + assert ingredient.price == PRICE_150