diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ab4f03f16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Виртуальные окружения Python +.env +.envrc +.venv +env/ +venv/ +ENV/ + +# Кэш Python +__pycache__/ +*.pyc + +# Настройки IDE и редакторов +.idea/ +.vscode/ + +# Системные файлы ОС +.DS_Store +Thumbs.db + +# Кэш и отчёты тестирования +.pytest_cache/ +.coverage +.coverage.* +htmlcov/ + +# Инструменты тестирования +.tox/ +.nox/ +.cache +.hypothesis/ + +# Артефакты сборки +dist/ +build/ \ No newline at end of file diff --git a/README.md b/README.md index 272081708..5b899cb16 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,50 @@ -## Задание 1: Юнит-тесты +# Задание 1: Юнит-тесты -### Автотесты для проверки программы, которая помогает заказать бургер в Stellar Burgers +## Автотесты для проверки программы заказа бургеров в Stellar Burgers -### Реализованные сценарии +В проекте реализованы юнит-тесты для проверки работы программы, которая помогает собрать и заказать бургер. -Созданы юнит-тесты, покрывающие классы `Bun`, `Burger`, `Ingredient`, `Database` +## Реализованные сценарии -Процент покрытия 100% (отчет: `htmlcov/index.html`) +Написаны тесты для следующих классов: -### Структура проекта +- `Bun` +- `Burger` +- `Ingredient` +- `Database` -- `praktikum` - пакет, содержащий код программы -- `tests` - пакет, содержащий тесты, разделенные по классам. Например, `bun_test.py`, `burger_test.py` и т.д. +В тестах используются: +- параметризация (`pytest.mark.parametrize`) +- моки (`unittest.mock`) -### Запуск автотестов +Тестовое покрытие: **78%** -**Установка зависимостей** +HTML-отчет о покрытии находится в файле: -> `$ pip install -r requirements.txt` +`htmlcov/index.html` -**Запуск автотестов и создание HTML-отчета о покрытии** +## Структура проекта -> `$ pytest --cov=praktikum --cov-report=html` +praktikum/ — основной код программы +tests/ — автотесты + +Тесты разделены по классам: + +- `test_bun.py` +- `test_burger.py` +- `test_ingredient.py` +- `test_database.py` + +## Запуск автотестов + +### Установка зависимостей + +`pip install -r requirements.txt` + +### Запуск тестов и генерация отчета о покрытии + +`pytest --cov=praktikum --cov-report=html` + +После выполнения отчёт можно открыть в браузере: + +`htmlcov/index.html` \ No newline at end of file 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 100% rename from bun.py rename to praktikum/bun.py diff --git a/burger.py b/praktikum/burger.py similarity index 99% rename from burger.py rename to praktikum/burger.py index 2b3b6a88b..a9604c6c6 100644 --- a/burger.py +++ b/praktikum/burger.py @@ -18,6 +18,7 @@ def __init__(self): def set_buns(self, bun: Bun): self.bun = bun + def add_ingredient(self, ingredient: Ingredient): self.ingredients.append(ingredient) 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..99dd8a6d5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest==9.0.1 +pytest-cov==7.0.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..56adfbd6f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,41 @@ +import sys +import os + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +import pytest +import praktikum.ingredient_types as ing_type + + +from unittest.mock import Mock +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from praktikum.burger import Burger + + +@pytest.fixture +def bun(): + mock_bun = Mock(spec_set=Bun) + mock_bun.get_name.return_value = 'black bun' + mock_bun.get_price.return_value = 100 + return mock_bun + +@pytest.fixture +def ingredient_filling(): + mock_ingridient = Mock(spec_set=Ingredient) + mock_ingridient.get_name.return_value = 'cutlet' + mock_ingridient.get_price.return_value = 100 + mock_ingridient.get_type.return_value = ing_type.INGREDIENT_TYPE_FILLING + return mock_ingridient + +@pytest.fixture +def ingredient_sauce(): + mock_ingridient = Mock(spec_set=Ingredient) + mock_ingridient.get_name.return_value = 'chili sauce' + mock_ingridient.get_price.return_value = 300 + mock_ingridient.get_type.return_value = ing_type.INGREDIENT_TYPE_SAUCE + return mock_ingridient + +@pytest.fixture +def burger(): + return Burger() \ No newline at end of file diff --git a/tests/data.py b/tests/data.py new file mode 100644 index 000000000..b1ab03715 --- /dev/null +++ b/tests/data.py @@ -0,0 +1,26 @@ +from praktikum.ingredient_types import INGREDIENT_TYPE_FILLING, INGREDIENT_TYPE_SAUCE + + +BUN = [ + ('black bun', 100), + ('white bun', 200), + ('red bun', 300) +] + +BUN_WITH_INDEX = [ + (0, 'black bun', 100), + (1, 'white bun', 200), + (2, 'red bun', 300) +] + +FILLING = [ + (INGREDIENT_TYPE_FILLING, "cutlet", 100), + (INGREDIENT_TYPE_FILLING, "dinosaur", 200), + (INGREDIENT_TYPE_FILLING, "sausage", 300), +] + +SAUCE = [ + (INGREDIENT_TYPE_SAUCE, "hot sauce", 100), + (INGREDIENT_TYPE_SAUCE, "sour cream", 200), + (INGREDIENT_TYPE_SAUCE, "chili sauce", 300), +] diff --git a/tests/test_bun.py b/tests/test_bun.py new file mode 100644 index 000000000..575c17826 --- /dev/null +++ b/tests/test_bun.py @@ -0,0 +1,13 @@ +import pytest + + +from praktikum.bun import Bun +from tests.data import BUN + +class TestBun: + @pytest.mark.parametrize('name, price', BUN) + def test_bun_getters(self, name, price): + bun = Bun(name, price) + + assert bun.get_name() == name + 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..4f4c011a6 --- /dev/null +++ b/tests/test_burger.py @@ -0,0 +1,44 @@ +class TestBurger: + def test_burger_initialization(self, burger): + assert burger.bun is None + assert burger.ingredients == [] + + def test_burger_set_buns(self, burger, bun): + burger.set_buns(bun) + assert burger.bun == bun + + def test_burger_add_ingredient(self, burger, ingredient_filling): + burger.add_ingredient(ingredient_filling) + assert len(burger.ingredients) == 1 + assert burger.ingredients[0] == ingredient_filling + + def test_burger_remove_ingredient(self, burger, ingredient_filling): + burger.add_ingredient(ingredient_filling) + burger.remove_ingredient(0) + assert burger.ingredients == [] + + def test_burger_move_ingredient(self, burger, ingredient_filling, ingredient_sauce): + burger.add_ingredient(ingredient_filling) + burger.add_ingredient(ingredient_sauce) + burger.move_ingredient(0, 1) + assert burger.ingredients == [ingredient_sauce, ingredient_filling] + + def test_burger_get_price(self,burger, bun, ingredient_filling, ingredient_sauce): + burger.set_buns(bun) + burger.add_ingredient(ingredient_filling) + burger.add_ingredient(ingredient_sauce) + assert burger.get_price() == 600 + + def test_get_receipt(self,burger, bun, ingredient_filling, ingredient_sauce): + burger.set_buns(bun) + burger.add_ingredient(ingredient_filling) + burger.add_ingredient(ingredient_sauce) + receipt = burger.get_receipt() + expected_receipt = ( + "(==== black bun ====)\n" + "= filling cutlet =\n" + "= sauce chili sauce =\n" + "(==== black bun ====)\n" + "Price: 600" + ) + assert receipt.replace("\n\n", "\n") == expected_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..eb564d3bc --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,45 @@ +import pytest + + +from praktikum.database import Database +from praktikum.bun import Bun +from praktikum.ingredient import Ingredient +from tests.data import FILLING, SAUCE, BUN_WITH_INDEX + + +class TestDatabase: + def test_database_has_three_buns(self): + db = Database() + assert len(db.available_buns()) == 3 + + def test_database_has_six_ingredients(self): + db = Database() + assert len(db.available_ingredients()) == 6 + + @pytest.mark.parametrize("index, name, price", BUN_WITH_INDEX) + def test_available_buns_content(self, index, name, price): + db = Database() + buns = db.available_buns() + assert buns[index].get_name() == name + assert buns[index].get_price() == price + + @pytest.mark.parametrize("ingredient_type, name, price", FILLING + SAUCE) + def test_available_ingredients_content(self, ingredient_type, name, price): + db = Database() + ingredients = db.available_ingredients() + assert any( + ingredient.get_type() == ingredient_type + and ingredient.get_name() == name + and ingredient.get_price() == price + for ingredient in ingredients + ) + + def test_database_returns_buns_as_bun_objects(self): + db = Database() + buns = db.available_buns() + assert all(isinstance(b, Bun) for b in buns) + + def test_database_returns_ingredients_as_ingredient_objects(self): + db = Database() + ingredients = db.available_ingredients() + assert all(isinstance(i, Ingredient) for i in ingredients) \ No newline at end of file diff --git a/tests/test_ingredient.py b/tests/test_ingredient.py new file mode 100644 index 000000000..85cc7ce24 --- /dev/null +++ b/tests/test_ingredient.py @@ -0,0 +1,13 @@ +import pytest + +from praktikum.ingredient import Ingredient +from tests.data import FILLING, SAUCE + +class TestIngredient: + @pytest.mark.parametrize('ingredient_type, name, price', FILLING + SAUCE) + def test_ingredient_getters(self, ingredient_type, name, price): + ingredient = Ingredient(ingredient_type, name, price) + + assert ingredient.get_type() == ingredient_type + assert ingredient.get_name() == name + assert ingredient.get_price() == price \ No newline at end of file