diff --git a/src/test_dictionary.py b/src/test_dictionary.py index 35e19fa..5bcacf1 100644 --- a/src/test_dictionary.py +++ b/src/test_dictionary.py @@ -1,6 +1,8 @@ from ex2_dictionary import calculate_price -from util import eidp_test, has_annotations, imported_modules_of, has_no_annotations +from util import * import pytest +import sys +import argparse from enum import Enum from copy import deepcopy @@ -72,7 +74,7 @@ def test_typeannotation_by_amount(): @pytest.mark.dependency(depends=[Task.A]) @pytest.mark.timeout(10) -@eidp_test("es wird nicht geschaut ob ein ein Produkt in `articles` ist", -2) +@eidp_test("nicht überprüft ob Produkte in `articles` enthalten", -2) def test_article_not_in_articles(): articles = {'Coke': 200} basket = {'Vitamine R': 1} @@ -98,11 +100,16 @@ def test_price_calc(): articles = {'Coke': 200, 'Eis': 350} basket = {'Coke': 2, 'Eis': 3} assert abs(calculate_price(articles, basket) - 14.5) < EPS + articles_dict = {"apples": 100, "oranges": 100, + "lemons": 200, "avocado": 500} + cart_dict = {"apples": 2, "oranges": 2, "lemons": 1, "bananas": 5} + + assert abs(calculate_price(articles_dict, cart_dict) - 6) < EPS @pytest.mark.dependency(depends=[Task.A]) @pytest.mark.timeout(10) -@eidp_test("`articles`/`cart` wird veraendert", -2) +@eidp_test("`articles`/`cart` wird verwendet", -2) def test_sideeffectss(): a = {'Coke': 200, 'Eis': 350} b = {'Coke': 2, 'Eis': 3} @@ -110,5 +117,74 @@ def test_sideeffectss(): assert a == a2 and b2 == b +@pytest.mark.dependency(depends=[Task.B]) +@pytest.mark.timeout(10) +@eidp_test("nicht überprüft ob Produkte in `articles` enthalten", -2) +def test_article_not_in_articles_by_amount(): + from ex2_dictionary import by_amount + articles_dict = {"apples_ex": 100, "oranges_ex": 100, + "lemons": 200, "avocado_ex": 500} + cart_dict = {"apples": 2, "oranges": 2, "lemons": 1, "bananas": 5} + + assert by_amount(articles_dict, cart_dict) == {1: ['lemons']} + + +@pytest.mark.dependency(depends=[Task.B]) +@pytest.mark.timeout(10) +@eidp_test("`by_amount` wird nicht korrekt berechnet", -2) +def test_by_amount(): + from ex2_dictionary import by_amount + articles_dict = {"apples": 100, "oranges": 100, + "lemons": 200, "avocado": 500} + cart_dict = {"apples": 2, "oranges": 2, "lemons": 1, "bananas": 5} + + assert by_amount(articles_dict, cart_dict) == { + 2: ['apples', 'oranges'], 1: ['lemons']} + + +@pytest.mark.dependency(depends=[Task.B]) +@pytest.mark.timeout(10) +@eidp_test("es wird kein neues Dictionary erstellt", -4) +def test_sideeffects_by_amount(): + from ex2_dictionary import by_amount + a = {"apples": 100, "oranges": 100, + "lemons": 200, "avocado": 500} + b = {"apples": 2, "oranges": 2, "lemons": 1, "bananas": 5} + + r = by_amount(a2 := deepcopy(a), b2 := deepcopy(b)) == { + 2: ['apples', 'oranges'], 1: ['lemons']} + assert a2 == a and b2 == b and r is not a and r is not b + + if __name__ == '__main__': - pass + verbose, testing, force = tuple(parse_args(sys.argv).values()) + parser = argparse.ArgumentParser( + prog='ex2_dictionary.py test', + description='testing for eidp 2023 ex2 dictionaries', + ) + parser.add_argument('-v', '--verbose', + help="don't print out test output", action="store_true") + parser.add_argument( + '-t', '--test_only', help='only test without writing readme', action="store_true") + args = parser.parse_args() + + # print(options) + # print(f'verbose := {verbose}, backup := {backup}') + + collector = ResultsCollector() + pytest.main(['test_dictionary.py', '-vv', '-s'], plugins=[collector]) + + readme_template = [] + passed, failed, skipped = collector.get_results() + + if args.verbose: + print(f"passed := {passed}") + print(f"failed := {failed}") + print(f"skipped := {skipped}") + + if not args.test_only: + tests = dict([(name, f) for name, f in vars() if callable( + f) and hasattr(f, "is_test")]) + passed_tests = list(map(lambda m: m[1], filter( + lambda m: m[0] in passed, tests))) + print(passed_tests) diff --git a/src/util.py b/src/util.py index d79b579..ff018db 100644 --- a/src/util.py +++ b/src/util.py @@ -1,6 +1,8 @@ from typing import Callable, Any, TypeAliasType, Iterator from types import ModuleType import inspect +import pytest +import time def has_annotations(f: Callable, param_types: list[object], return_type: object = None) -> bool: @@ -34,6 +36,61 @@ def eidp_test(message: str, minus_points: float) -> Callable[[Callable[..., Any] 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")]) +class ResultsCollector: + def __init__(self): + self.reports = [] + self.collected = 0 + self.exitcode = 0 + self.passed = 0 + self.failed = 0 + self.xfailed = 0 + self.skipped = 0 + self.total_duration = 0 + + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_makereport(self, item, call): + outcome = yield + report = outcome.get_result() + if report.when == 'call': + self.reports.append(report) + + def pytest_collection_modifyitems(self, items): + self.collected = len(items) + + def pytest_terminal_summary(self, terminalreporter, exitstatus): + self.passed = len(terminalreporter.stats.get('passed', [])) + self.failed = len(terminalreporter.stats.get('failed', [])) + self.xfailed = len(terminalreporter.stats.get('xfailed', [])) + self.skipped = len(terminalreporter.stats.get('skipped', [])) + self.total_duration = time.time() - terminalreporter._sessionstarttime + + def get_results(self) -> tuple[list[str], list[str], list[str]]: + passed = [] + failed = [] + skipped = [] + + for report in self.reports: + if report.outcome == 'passed': + passed.append(report.nodeid.split('::')[-1]) + elif report.outcome == 'failed': + failed.append(report.nodeid.split('::')[-1]) + elif report.outcome == 'skipped': + skipped.append(report.nodeid.split('::')[-1]) + + return passed, failed, skipped + + +def parse_args(args: list[str]) -> dict[str, bool]: + arguments = args[1:] + retval = {'verbose': False, 'test': False, 'force': False} + for argument in arguments: + _args = argument[1:] if argument[0] == '-' else argument + for arg in _args: + match arg: + case 'v': + retval['verbose'] = True + case 't': + retval['test'] = True + case 'f': + retval['force'] = True + return retval