From 0b35054f9db84f443dbfe1e61a377eef5c80495b Mon Sep 17 00:00:00 2001 From: Benjamin Townsend Date: Mon, 8 Apr 2024 15:19:29 -0700 Subject: [PATCH] Translate several doctest tests to pytest --- .gitignore | 3 ++ patterns/behavioral/catalog.py | 79 +++++++++++++------------------ patterns/behavioral/mediator.py | 10 ++-- patterns/behavioral/memento.py | 2 +- patterns/behavioral/visitor.py | 10 ++-- tests/behavioral/test_catalog.py | 23 +++++++++ tests/behavioral/test_mediator.py | 16 +++++++ tests/behavioral/test_memento.py | 29 ++++++++++++ tests/behavioral/test_visitor.py | 22 +++++++++ 9 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 tests/behavioral/test_catalog.py create mode 100644 tests/behavioral/test_mediator.py create mode 100644 tests/behavioral/test_memento.py create mode 100644 tests/behavioral/test_visitor.py diff --git a/.gitignore b/.gitignore index aaf2ded3..c4d95ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ venv/ .vscode/ .python-version .coverage +.project +.pydevproject +/.pytest_cache/ diff --git a/patterns/behavioral/catalog.py b/patterns/behavioral/catalog.py index 7c91aa7d..70deba62 100644 --- a/patterns/behavioral/catalog.py +++ b/patterns/behavioral/catalog.py @@ -1,15 +1,13 @@ """ -A class that uses different static function depending of a parameter passed in -init. Note the use of a single dictionary instead of multiple conditions +A class that uses a different static function depending on a parameter passed in +init. Note the use of a single dictionary instead of multiple conditions. """ __author__ = "Ibrahim Diop " class Catalog: - """catalog of multiple static methods that are executed depending on an init - - parameter + """catalog of multiple static methods that are executed depending on an init parameter """ def __init__(self, param: str) -> None: @@ -29,27 +27,24 @@ def __init__(self, param: str) -> None: raise ValueError(f"Invalid Value for Param: {param}") @staticmethod - def _static_method_1() -> None: - print("executed method 1!") + def _static_method_1() -> str: + return "executed method 1!" @staticmethod - def _static_method_2() -> None: - print("executed method 2!") + def _static_method_2() -> str: + return "executed method 2!" - def main_method(self) -> None: + def main_method(self) -> str: """will execute either _static_method_1 or _static_method_2 depending on self.param value """ - self._static_method_choices[self.param]() + return self._static_method_choices[self.param]() # Alternative implementation for different levels of methods class CatalogInstance: - - """catalog of multiple methods that are executed depending on an init - - parameter + """catalog of multiple methods that are executed depending on an init parameter """ def __init__(self, param: str) -> None: @@ -61,31 +56,28 @@ def __init__(self, param: str) -> None: else: raise ValueError(f"Invalid Value for Param: {param}") - def _instance_method_1(self) -> None: - print(f"Value {self.x1}") + def _instance_method_1(self) -> str: + return f"Value {self.x1}" - def _instance_method_2(self) -> None: - print(f"Value {self.x2}") + def _instance_method_2(self) -> str: + return f"Value {self.x2}" _instance_method_choices = { "param_value_1": _instance_method_1, "param_value_2": _instance_method_2, } - def main_method(self) -> None: + def main_method(self) -> str: """will execute either _instance_method_1 or _instance_method_2 depending on self.param value """ - self._instance_method_choices[self.param].__get__(self)() # type: ignore + return self._instance_method_choices[self.param].__get__(self)() # type: ignore # type ignore reason: https://github.com/python/mypy/issues/10206 class CatalogClass: - - """catalog of multiple class methods that are executed depending on an init - - parameter + """catalog of multiple class methods that are executed depending on an init parameter """ x1 = "x1" @@ -99,32 +91,29 @@ def __init__(self, param: str) -> None: raise ValueError(f"Invalid Value for Param: {param}") @classmethod - def _class_method_1(cls) -> None: - print(f"Value {cls.x1}") + def _class_method_1(cls) -> str: + return f"Value {cls.x1}" @classmethod - def _class_method_2(cls) -> None: - print(f"Value {cls.x2}") + def _class_method_2(cls) -> str: + return f"Value {cls.x2}" _class_method_choices = { "param_value_1": _class_method_1, "param_value_2": _class_method_2, } - def main_method(self): + def main_method(self) -> str: """will execute either _class_method_1 or _class_method_2 depending on self.param value """ - self._class_method_choices[self.param].__get__(None, self.__class__)() # type: ignore + return self._class_method_choices[self.param].__get__(None, self.__class__)() # type: ignore # type ignore reason: https://github.com/python/mypy/issues/10206 class CatalogStatic: - - """catalog of multiple static methods that are executed depending on an init - - parameter + """catalog of multiple static methods that are executed depending on an init parameter """ def __init__(self, param: str) -> None: @@ -135,25 +124,25 @@ def __init__(self, param: str) -> None: raise ValueError(f"Invalid Value for Param: {param}") @staticmethod - def _static_method_1() -> None: - print("executed method 1!") + def _static_method_1() -> str: + return "executed method 1!" @staticmethod - def _static_method_2() -> None: - print("executed method 2!") + def _static_method_2() -> str: + return "executed method 2!" _static_method_choices = { "param_value_1": _static_method_1, "param_value_2": _static_method_2, } - def main_method(self) -> None: + def main_method(self) -> str: """will execute either _static_method_1 or _static_method_2 depending on self.param value """ - self._static_method_choices[self.param].__get__(None, self.__class__)() # type: ignore + return self._static_method_choices[self.param].__get__(None, self.__class__)() # type: ignore # type ignore reason: https://github.com/python/mypy/issues/10206 @@ -161,19 +150,19 @@ def main(): """ >>> test = Catalog('param_value_2') >>> test.main_method() - executed method 2! + 'executed method 2!' >>> test = CatalogInstance('param_value_1') >>> test.main_method() - Value x1 + 'Value x1' >>> test = CatalogClass('param_value_2') >>> test.main_method() - Value x2 + 'Value x2' >>> test = CatalogStatic('param_value_1') >>> test.main_method() - executed method 1! + 'executed method 1!' """ diff --git a/patterns/behavioral/mediator.py b/patterns/behavioral/mediator.py index e4b3c34a..6a59bbb6 100644 --- a/patterns/behavioral/mediator.py +++ b/patterns/behavioral/mediator.py @@ -15,7 +15,7 @@ class ChatRoom: """Mediator class""" def display_message(self, user: User, message: str) -> None: - print(f"[{user} says]: {message}") + return f"[{user} says]: {message}" class User: @@ -26,7 +26,7 @@ def __init__(self, name: str) -> None: self.chat_room = ChatRoom() def say(self, message: str) -> None: - self.chat_room.display_message(self, message) + return self.chat_room.display_message(self, message) def __str__(self) -> str: return self.name @@ -39,11 +39,11 @@ def main(): >>> ethan = User('Ethan') >>> molly.say("Hi Team! Meeting at 3 PM today.") - [Molly says]: Hi Team! Meeting at 3 PM today. + '[Molly says]: Hi Team! Meeting at 3 PM today.' >>> mark.say("Roger that!") - [Mark says]: Roger that! + '[Mark says]: Roger that!' >>> ethan.say("Alright.") - [Ethan says]: Alright. + '[Ethan says]: Alright.' """ diff --git a/patterns/behavioral/memento.py b/patterns/behavioral/memento.py index e1d42fc2..8c101c75 100644 --- a/patterns/behavioral/memento.py +++ b/patterns/behavioral/memento.py @@ -42,7 +42,7 @@ def rollback(self): class Transactional: - """Adds transactional semantics to methods. Methods decorated with + """Adds transactional semantics to methods. Methods decorated with @Transactional will rollback to entry-state upon exceptions. """ diff --git a/patterns/behavioral/visitor.py b/patterns/behavioral/visitor.py index 00d95248..2e11a583 100644 --- a/patterns/behavioral/visitor.py +++ b/patterns/behavioral/visitor.py @@ -46,10 +46,10 @@ def visit(self, node, *args, **kwargs): return meth(node, *args, **kwargs) def generic_visit(self, node, *args, **kwargs): - print("generic_visit " + node.__class__.__name__) + return "generic_visit " + node.__class__.__name__ def visit_B(self, node, *args, **kwargs): - print("visit_B " + node.__class__.__name__) + return "visit_B " + node.__class__.__name__ def main(): @@ -58,13 +58,13 @@ def main(): >>> visitor = Visitor() >>> visitor.visit(a) - generic_visit A + 'generic_visit A' >>> visitor.visit(b) - visit_B B + 'visit_B B' >>> visitor.visit(c) - visit_B C + 'visit_B C' """ diff --git a/tests/behavioral/test_catalog.py b/tests/behavioral/test_catalog.py new file mode 100644 index 00000000..60933816 --- /dev/null +++ b/tests/behavioral/test_catalog.py @@ -0,0 +1,23 @@ +import pytest + +from patterns.behavioral.catalog import Catalog, CatalogClass, CatalogInstance, CatalogStatic + +def test_catalog_multiple_methods(): + test = Catalog('param_value_2') + token = test.main_method() + assert token == 'executed method 2!' + +def test_catalog_multiple_instance_methods(): + test = CatalogInstance('param_value_1') + token = test.main_method() + assert token == 'Value x1' + +def test_catalog_multiple_class_methods(): + test = CatalogClass('param_value_2') + token = test.main_method() + assert token == 'Value x2' + +def test_catalog_multiple_static_methods(): + test = CatalogStatic('param_value_1') + token = test.main_method() + assert token == 'executed method 1!' diff --git a/tests/behavioral/test_mediator.py b/tests/behavioral/test_mediator.py new file mode 100644 index 00000000..1af60e67 --- /dev/null +++ b/tests/behavioral/test_mediator.py @@ -0,0 +1,16 @@ +import pytest + +from patterns.behavioral.mediator import User + +def test_mediated_comments(): + molly = User('Molly') + mediated_comment = molly.say("Hi Team! Meeting at 3 PM today.") + assert mediated_comment == "[Molly says]: Hi Team! Meeting at 3 PM today." + + mark = User('Mark') + mediated_comment = mark.say("Roger that!") + assert mediated_comment == "[Mark says]: Roger that!" + + ethan = User('Ethan') + mediated_comment = ethan.say("Alright.") + assert mediated_comment == "[Ethan says]: Alright." diff --git a/tests/behavioral/test_memento.py b/tests/behavioral/test_memento.py new file mode 100644 index 00000000..bd307b76 --- /dev/null +++ b/tests/behavioral/test_memento.py @@ -0,0 +1,29 @@ +import pytest + +from patterns.behavioral.memento import NumObj, Transaction + +def test_object_creation(): + num_obj = NumObj(-1) + assert repr(num_obj) == '', "Object representation not as expected" + +def test_rollback_on_transaction(): + num_obj = NumObj(-1) + a_transaction = Transaction(True, num_obj) + for _i in range(3): + num_obj.increment() + a_transaction.commit() + assert num_obj.value == 2 + + for _i in range(3): + num_obj.increment() + try: + num_obj.value += 'x' # will fail + except TypeError: + a_transaction.rollback() + assert num_obj.value == 2, "Transaction did not rollback as expected" + +def test_rollback_with_transactional_annotation(): + num_obj = NumObj(2) + with pytest.raises(TypeError): + num_obj.do_stuff() + assert num_obj.value == 2 diff --git a/tests/behavioral/test_visitor.py b/tests/behavioral/test_visitor.py new file mode 100644 index 00000000..31d230de --- /dev/null +++ b/tests/behavioral/test_visitor.py @@ -0,0 +1,22 @@ +import pytest + +from patterns.behavioral.visitor import A, B, C, Visitor + +@pytest.fixture +def visitor(): + return Visitor() + +def test_visiting_generic_node(visitor): + a = A() + token = visitor.visit(a) + assert token == 'generic_visit A', "The expected generic object was not called" + +def test_visiting_specific_nodes(visitor): + b = B() + token = visitor.visit(b) + assert token == 'visit_B B', "The expected specific object was not called" + +def test_visiting_inherited_nodes(visitor): + c = C() + token = visitor.visit(c) + assert token == 'visit_B C', "The expected inherited object was not called"