diff --git a/src/ex2_dictionary.py b/src/ex2_dictionary.py new file mode 100644 index 0000000..a133a77 --- /dev/null +++ b/src/ex2_dictionary.py @@ -0,0 +1,29 @@ +# DO NOT CHANGE THE IMPORTS! + +def calculate_price(articles: dict[str, int], cart: dict[str, int]) -> float: + price = 0 + for name, amount in cart.items(): + if name in articles: + price += articles[name] * amount + return price / 100 + + +def by_amount(articles: dict[str, int], cart: dict[str, int]) -> dict[int, list[str]]: + out: dict[int, list[str]] = dict() + for name, amount in cart.items(): + if name not in articles: + continue + if amount not in out: + out[amount] = [name] + else: + out[amount] += [name] + return out + + +if __name__ == "__main__": + articles_dict = {"apples": 100, "oranges": 100, "lemons": 200, "avocado": 500} + cart_dict = {"apples": 2, "oranges": 2, "lemons": 1, "bananas": 5} + + assert calculate_price(articles_dict, cart_dict) == 6.0 + + assert by_amount(articles_dict, cart_dict) == {2: ['apples', 'oranges'], 1: ['lemons']} diff --git a/src/test_dictionary.py b/src/test_dictionary.py new file mode 100644 index 0000000..35e19fa --- /dev/null +++ b/src/test_dictionary.py @@ -0,0 +1,114 @@ +from ex2_dictionary import calculate_price +from util import eidp_test, has_annotations, imported_modules_of, has_no_annotations +import pytest +from enum import Enum +from copy import deepcopy + + +class Task(Enum): + MODULES, A, B = 'modules', 'calculate_price', 'by_amount' + + +EPS = 0.001 + + +@pytest.mark.dependency(name=Task.MODULES) +@pytest.mark.timeout(10) +@eidp_test("unerlaubte Module importiert", -10) +def test_imported_modules(): + import ex2_dictionary + assert len(list(imported_modules_of(ex2_dictionary))) == 0 + + +@pytest.mark.dependency(depends=[Task.MODULES]) +@pytest.mark.dependency(name=Task.A) +@pytest.mark.timeout(10) +@eidp_test("`calculate_price` nicht implementiert", -10) +def test_calculate_price_implemented(): + from ex2_dictionary import calculate_price as _ + + +@pytest.mark.dependency(depends=[Task.MODULES]) +@pytest.mark.dependency(name=Task.B) +@pytest.mark.timeout(10) +@eidp_test("`by_amount` nicht implementiert", -10) +def test_by_amount_implemented(): + from ex2_dictionary import by_amount as _ + + +@pytest.mark.dependency(depends=[Task.A]) +@pytest.mark.timeout(10) +@eidp_test("`calculate_price` hat keine Typannotationen", -1) +def test_no_typeannotation_calculate_price(): + from ex2_dictionary import calculate_price + assert not has_no_annotations(calculate_price) + + +@pytest.mark.dependency(depends=[Task.A]) +@pytest.mark.timeout(10) +@eidp_test("`calculate_price` hat inkorrekte Typannotationen", -0.5) +def test_typeannotation_calculate_price(): + from ex2_dictionary import calculate_price + assert has_annotations( + calculate_price, [dict[str, int], dict[str, int]], float) + + +@pytest.mark.dependency(depends=[Task.B]) +@pytest.mark.timeout(10) +@eidp_test("`by_amount` hat keine Typannotationen", -1) +def test_no_typeannotation_by_amount(): + from ex2_dictionary import by_amount + assert not has_no_annotations(by_amount) + + +@pytest.mark.dependency(depends=[Task.B]) +@pytest.mark.timeout(10) +@eidp_test("`by_amount` hat inkorrekte Typannotationen", -0.5) +def test_typeannotation_by_amount(): + from ex2_dictionary import by_amount + assert has_annotations( + by_amount, [dict[str, int], dict[str, int]], dict[int, list[str]]) + + +@pytest.mark.dependency(depends=[Task.A]) +@pytest.mark.timeout(10) +@eidp_test("es wird nicht geschaut ob ein ein Produkt in `articles` ist", -2) +def test_article_not_in_articles(): + articles = {'Coke': 200} + basket = {'Vitamine R': 1} + assert abs(calculate_price(articles, basket)) < EPS + + +@pytest.mark.dependency(depends=[Task.A]) +@pytest.mark.timeout(10) +@eidp_test("Preis wird nicht in Euro (float) ausgegeben", -2) +def test_price_in_euro(): + articles = {'Coke': 200} + basket = {'Vitamine R': 1, 'Coke': 2} + assert abs(calculate_price(articles, basket) - 4) < EPS + + +@pytest.mark.dependency(depends=[Task.A]) +@pytest.mark.timeout(10) +@eidp_test("Preis wird nicht korrekt berechnet", -2) +def test_price_calc(): + articles = {'Coke': 200, 'Eis': 350} + basket = {'Coke': 0} + assert abs(calculate_price(articles, basket)) < EPS + articles = {'Coke': 200, 'Eis': 350} + basket = {'Coke': 2, 'Eis': 3} + assert abs(calculate_price(articles, basket) - 14.5) < EPS + + +@pytest.mark.dependency(depends=[Task.A]) +@pytest.mark.timeout(10) +@eidp_test("`articles`/`cart` wird veraendert", -2) +def test_sideeffectss(): + a = {'Coke': 200, 'Eis': 350} + b = {'Coke': 2, 'Eis': 3} + calculate_price(a2 := deepcopy(a), b2 := deepcopy(b)) + assert a == a2 and b2 == b + + +if __name__ == '__main__': + pass diff --git a/src/util.py b/src/util.py new file mode 100644 index 0000000..d79b579 --- /dev/null +++ b/src/util.py @@ -0,0 +1,39 @@ +from typing import Callable, Any, TypeAliasType, Iterator +from types import ModuleType +import inspect + + +def has_annotations(f: Callable, param_types: list[object], return_type: object = None) -> bool: + a = f.__annotations__ + ret = a['return'] if 'return' in a else None + params = list(a.values())[:-1] if 'return' in a else list(a.values()) + for param, param_type in zip(params, param_types): + if isinstance(param, TypeAliasType): + param = param.__value__ + if isinstance(param_type, TypeAliasType): + param_type = param_type.__value__ + if param != param_type: + return False + return ret == return_type + + +def has_no_annotations(f: Callable) -> bool: + return len(f.__annotations__) == 0 + + +def imported_modules_of(module: ModuleType) -> Iterator[ModuleType]: + return map(lambda m: m[1], filter(lambda m: inspect.ismodule(m[1]), inspect.getmembers(module))) + + +def eidp_test(message: str, minus_points: float) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + def wrapper(test: Callable[..., Any]) -> Callable[..., Any]: + test.message = message + test.minus_points = minus_points + test.is_test = True + return test + return wrapper + + +def get_tests(module: ModuleType) -> dict[str, Callable[..., Any]]: + return dict([(name, f) for name, f in module.__dict__ + if callable(f) and hasattr(f, "is_test")])