From 5777369fda1fcc6483641fc3aba21ec202ef708c Mon Sep 17 00:00:00 2001 From: Grygorii Iermolenko Date: Fri, 1 Mar 2019 12:55:00 -0500 Subject: [PATCH 1/3] Combine and refine dependency injection patterns --- patterns/dependency_injection.py | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 patterns/dependency_injection.py diff --git a/patterns/dependency_injection.py b/patterns/dependency_injection.py new file mode 100644 index 00000000..bd795918 --- /dev/null +++ b/patterns/dependency_injection.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding : utf-8 -*- + +""" +Port of the Java example of "Constructor Injection" in +"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros +(ISBN-10: 0131495054, ISBN-13: 978-0131495050) + +production code which is untestable: + +class TimeDisplay(object): + + def __init__(self): + self.time_provider = datetime.datetime + + def get_current_time_as_html_fragment(self): + current_time = self.time_provider.now() + current_time_as_html_fragment = "{}".format(current_time) + return current_time_as_html_fragment +""" + +import datetime + + +class ConstructorInjection(object): + + def __init__(self, time_provider): + self.time_provider = time_provider + + def get_current_time_as_html_fragment(self): + current_time = self.time_provider() + current_time_as_html_fragment = "{}".format(current_time) + return current_time_as_html_fragment + + +class ParameterInjection(object): + + def __init__(self): + pass + + def get_current_time_as_html_fragment(self, time_provider): + current_time = time_provider() + current_time_as_html_fragment = "{}".format(current_time) + return current_time_as_html_fragment + + +class SetterInjection(object): + """Setter Injection""" + + def __init__(self): + pass + + def set_time_provider(self, time_provider): + self.time_provider = time_provider + + def get_current_time_as_html_fragment(self): + current_time = self.time_provider() + current_time_as_html_fragment = "{}".format(current_time) + return current_time_as_html_fragment + + +def production_code_time_provider(): + """ + Production code version of the time provider (just a wrapper for formatting + datetime for this example). + """ + current_time = datetime.datetime.now() + current_time_formatted = "{}:{}".format(current_time.hour, current_time.minute) + return current_time_formatted + + +def midnight_time_provider(): + """Hard-coded stub""" + return "24:01" + + +def main(): + """ + >>> time_with_ci1 = ConstructorInjection(midnight_time_provider) + >>> time_with_ci1.get_current_time_as_html_fragment() + '24:01' + + >>> time_with_ci2 = ConstructorInjection(production_code_time_provider) + >>> time_with_ci2.get_current_time_as_html_fragment() + '...' + + >>> time_with_pi = ParameterInjection() + >>> time_with_pi.get_current_time_as_html_fragment(midnight_time_provider) + '24:01' + + >>> time_with_si = SetterInjection() + + # >>> time_with_si.get_current_time_as_html_fragment() + # AttributeError: 'SetterInjection' object has no attribute 'time_provider' + + >>> time_with_si.set_time_provider(midnight_time_provider) + >>> time_with_si.get_current_time_as_html_fragment() + '24:01' + """ + + +if __name__ == "__main__": + import doctest + doctest.testmod(optionflags=doctest.ELLIPSIS) From 9fe9746f60e1be19735063f6d1b5c9f5c36a08ed Mon Sep 17 00:00:00 2001 From: Grygorii Iermolenko Date: Fri, 1 Mar 2019 15:54:03 -0500 Subject: [PATCH 2/3] Remove old injection examples, update readme --- README.md | 2 +- patterns/dependency_injection.py | 6 ++- patterns/dft/__init__.py | 0 patterns/dft/constructor_injection.py | 53 ----------------------- patterns/dft/parameter_injection.py | 54 ----------------------- patterns/dft/setter_injection.py | 57 ------------------------- tests/dft/test_constructor_injection.py | 41 ------------------ tests/dft/test_parameter_injection.py | 46 -------------------- tests/dft/test_setter_injection.py | 46 -------------------- 9 files changed, 5 insertions(+), 300 deletions(-) delete mode 100644 patterns/dft/__init__.py delete mode 100644 patterns/dft/constructor_injection.py delete mode 100644 patterns/dft/parameter_injection.py delete mode 100644 patterns/dft/setter_injection.py delete mode 100644 tests/dft/test_constructor_injection.py delete mode 100644 tests/dft/test_parameter_injection.py delete mode 100644 tests/dft/test_setter_injection.py diff --git a/README.md b/README.md index ebc2b669..20739846 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ __Design for Testability Patterns__: | Pattern | Description | |:-------:| ----------- | -| [setter_injection](patterns/dft/setter_injection.py) | the client provides the depended-on object to the SUT via the setter injection (implementation variant of dependency injection) | +| [dependency_injection](patterns/dependency_injection.py) | 3 variants of dependency injection | __Fundamental Patterns__: diff --git a/patterns/dependency_injection.py b/patterns/dependency_injection.py index bd795918..a58cec3f 100644 --- a/patterns/dependency_injection.py +++ b/patterns/dependency_injection.py @@ -90,8 +90,10 @@ def main(): >>> time_with_si = SetterInjection() - # >>> time_with_si.get_current_time_as_html_fragment() - # AttributeError: 'SetterInjection' object has no attribute 'time_provider' + >>> time_with_si.get_current_time_as_html_fragment() + Traceback (most recent call last): + ... + AttributeError: 'SetterInjection' object has no attribute 'time_provider' >>> time_with_si.set_time_provider(midnight_time_provider) >>> time_with_si.get_current_time_as_html_fragment() diff --git a/patterns/dft/__init__.py b/patterns/dft/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/patterns/dft/constructor_injection.py b/patterns/dft/constructor_injection.py deleted file mode 100644 index 7194a2ea..00000000 --- a/patterns/dft/constructor_injection.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/python -# -*- coding : utf-8 -*- -import datetime - -""" -Port of the Java example of "Constructor Injection" in -"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros -(ISBN-10: 0131495054, ISBN-13: 978-0131495050) - -production code which is untestable: - -class TimeDisplay(object): - - def __init__(self): - self.time_provider = datetime.datetime - - def get_current_time_as_html_fragment(self): - current_time = self.time_provider.now() - current_time_as_html_fragment = "{}".format(current_time) - return current_time_as_html_fragment -""" - - -class TimeDisplay(object): - def __init__(self, time_provider): - self.time_provider = time_provider - - def get_current_time_as_html_fragment(self): - current_time = self.time_provider.now() - current_time_as_html_fragment = "{}".format(current_time) - return current_time_as_html_fragment - - -class ProductionCodeTimeProvider(object): - """ - Production code version of the time provider (just a wrapper for formatting - datetime for this example). - """ - - def now(self): - current_time = datetime.datetime.now() - current_time_formatted = "{}:{}".format(current_time.hour, current_time.minute) - return current_time_formatted - - -class MidnightTimeProvider(object): - """ - Class implemented as hard-coded stub (in contrast to configurable stub). - """ - - def now(self): - current_time_is_always_midnight = "24:01" - return current_time_is_always_midnight diff --git a/patterns/dft/parameter_injection.py b/patterns/dft/parameter_injection.py deleted file mode 100644 index c1592736..00000000 --- a/patterns/dft/parameter_injection.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/python -# -*- coding : utf-8 -*- -import datetime - -""" -Port of the Java example of "Parameter Injection" in -"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros -(ISBN-10: 0131495054, ISBN-13: 978-0131495050) accessible in outdated version on -http://xunitpatterns.com/Dependency%20Injection.html. - -production code which is untestable: - -class TimeDisplay(object): - - def __init__(self): - self.time_provider = datetime.datetime - - def get_current_time_as_html_fragment(self): - current_time = self.time_provider.now() - current_time_as_html_fragment = "{}".format(current_time) - return current_time_as_html_fragment -""" - - -class TimeDisplay(object): - def __init__(self): - pass - - def get_current_time_as_html_fragment(self, time_provider): - current_time = time_provider.now() - current_time_as_html_fragment = "{}".format(current_time) - return current_time_as_html_fragment - - -class ProductionCodeTimeProvider(object): - """ - Production code version of the time provider (just a wrapper for formatting - datetime for this example). - """ - - def now(self): - current_time = datetime.datetime.now() - current_time_formatted = "{}:{}".format(current_time.hour, current_time.minute) - return current_time_formatted - - -class MidnightTimeProvider(object): - """ - Class implemented as hard-coded stub (in contrast to configurable stub). - """ - - def now(self): - current_time_is_always_midnight = "24:01" - return current_time_is_always_midnight diff --git a/patterns/dft/setter_injection.py b/patterns/dft/setter_injection.py deleted file mode 100644 index f14a2a24..00000000 --- a/patterns/dft/setter_injection.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/python -# -*- coding : utf-8 -*- -import datetime - -""" -Port of the Java example of "Setter Injection" in -"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros -(ISBN-10: 0131495054, ISBN-13: 978-0131495050) accessible in outdated version on -http://xunitpatterns.com/Dependency%20Injection.html. - -production code which is untestable: - -class TimeDisplay(object): - - def __init__(self): - self.time_provider = datetime.datetime - - def get_current_time_as_html_fragment(self): - current_time = self.time_provider.now() - current_time_as_html_fragment = "{}".format(current_time) - return current_time_as_html_fragment -""" - - -class TimeDisplay(object): - def __init__(self): - pass - - def set_time_provider(self, time_provider): - self.time_provider = time_provider - - def get_current_time_as_html_fragment(self): - current_time = self.time_provider.now() - current_time_as_html_fragment = "{}".format(current_time) - return current_time_as_html_fragment - - -class ProductionCodeTimeProvider(object): - """ - Production code version of the time provider (just a wrapper for formatting - datetime for this example). - """ - - def now(self): - current_time = datetime.datetime.now() - current_time_formatted = "{}:{}".format(current_time.hour, current_time.minute) - return current_time_formatted - - -class MidnightTimeProvider(object): - """ - Class implemented as hard-coded stub (in contrast to configurable stub). - """ - - def now(self): - current_time_is_always_midnight = "24:01" - return current_time_is_always_midnight diff --git a/tests/dft/test_constructor_injection.py b/tests/dft/test_constructor_injection.py deleted file mode 100644 index 6ee83601..00000000 --- a/tests/dft/test_constructor_injection.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import unittest - -from patterns.dft.constructor_injection import TimeDisplay, MidnightTimeProvider, ProductionCodeTimeProvider, datetime - -""" -Port of the Java example of "Constructor Injection" in -"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros -(ISBN-10: 0131495054, ISBN-13: 978-0131495050) - -Test code which will almost always fail (if not exactly 12:01) when untestable -production code (production code time provider is datetime) is used: - - def test_display_current_time_at_midnight(self): - class_under_test = TimeDisplay() - expected_time = "24:01" - result = class_under_test.get_current_time_as_as_html_fragment() - self.assertEqual(result, expected_time) -""" - - -class ConstructorInjectionTest(unittest.TestCase): - def test_display_current_time_at_midnight(self): - """ - Will almost always fail (despite of right at/after midnight). - """ - time_provider_stub = MidnightTimeProvider() - class_under_test = TimeDisplay(time_provider_stub) - expected_time = "24:01" - self.assertEqual(class_under_test.get_current_time_as_html_fragment(), expected_time) - - def test_display_current_time_at_current_time(self): - """ - Just as justification for working example. (Will always pass.) - """ - production_code_time_provider = ProductionCodeTimeProvider() - class_under_test = TimeDisplay(production_code_time_provider) - current_time = datetime.datetime.now() - expected_time = "{}:{}".format(current_time.hour, current_time.minute) - self.assertEqual(class_under_test.get_current_time_as_html_fragment(), expected_time) diff --git a/tests/dft/test_parameter_injection.py b/tests/dft/test_parameter_injection.py deleted file mode 100644 index da340b93..00000000 --- a/tests/dft/test_parameter_injection.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import unittest - -from patterns.dft.parameter_injection import TimeDisplay, MidnightTimeProvider, ProductionCodeTimeProvider, datetime - -""" -Port of the Java example of "Parameter Injection" in -"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros -(ISBN-10: 0131495054, ISBN-13: 978-0131495050) accessible in outdated version on -http://xunitpatterns.com/Dependency%20Injection.html. - -Test code which will almost always fail (if not exactly 12:01) when untestable -production code (have a look into constructor_injection.py) is used: - - def test_display_current_time_at_midnight(self): - class_under_test = TimeDisplay() - expected_time = "24:01" - result = class_under_test.get_current_time_as_as_html_fragment() - self.assertEqual(result, expected_time) -""" - - -class ParameterInjectionTest(unittest.TestCase): - def test_display_current_time_at_midnight(self): - """ - Would almost always fail (despite of right at/after midnight) if - untestable production code would have been used. - """ - time_provider_stub = MidnightTimeProvider() - class_under_test = TimeDisplay() - expected_time = "24:01" - self.assertEqual(class_under_test.get_current_time_as_html_fragment(time_provider_stub), expected_time) - - def test_display_current_time_at_current_time(self): - """ - Just as justification for working example with the time provider used in - production. (Will always pass.) - """ - production_code_time_provider = ProductionCodeTimeProvider() - class_under_test = TimeDisplay() - current_time = datetime.datetime.now() - expected_time = "{}:{}".format(current_time.hour, current_time.minute) - self.assertEqual( - class_under_test.get_current_time_as_html_fragment(production_code_time_provider), expected_time - ) diff --git a/tests/dft/test_setter_injection.py b/tests/dft/test_setter_injection.py deleted file mode 100644 index fce7b2c9..00000000 --- a/tests/dft/test_setter_injection.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import unittest - -from patterns.dft.setter_injection import TimeDisplay, MidnightTimeProvider, ProductionCodeTimeProvider, datetime - -""" -Port of the Java example of "Setter Injection" in -"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros -(ISBN-10: 0131495054, ISBN-13: 978-0131495050) accessible in outdated version on -http://xunitpatterns.com/Dependency%20Injection.html. - -Test code which will almost always fail (if not exactly 12:01) when untestable -production code (have a look into constructor_injection.py) is used: - - def test_display_current_time_at_midnight(self): - class_under_test = TimeDisplay() - expected_time = "24:01" - result = class_under_test.get_current_time_as_as_html_fragment() - self.assertEqual(result, expected_time) -""" - - -class ParameterInjectionTest(unittest.TestCase): - def test_display_current_time_at_midnight(self): - """ - Would almost always fail (despite of right at/after midnight) if - untestable production code would have been used. - """ - time_provider_stub = MidnightTimeProvider() - class_under_test = TimeDisplay() - class_under_test.set_time_provider(time_provider_stub) - expected_time = "24:01" - self.assertEqual(class_under_test.get_current_time_as_html_fragment(), expected_time) - - def test_display_current_time_at_current_time(self): - """ - Just as justification for working example with the time provider used in - production. (Will always pass.) - """ - production_code_time_provider = ProductionCodeTimeProvider() - class_under_test = TimeDisplay() - class_under_test.set_time_provider(production_code_time_provider) - current_time = datetime.datetime.now() - expected_time = "{}:{}".format(current_time.hour, current_time.minute) - self.assertEqual(class_under_test.get_current_time_as_html_fragment(), expected_time) From fe7d3a13e4b403530008d4e9c29609bf08dc715b Mon Sep 17 00:00:00 2001 From: Grygorii Iermolenko Date: Fri, 1 Mar 2019 16:18:12 -0500 Subject: [PATCH 3/3] Update pattern description --- patterns/dependency_injection.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/patterns/dependency_injection.py b/patterns/dependency_injection.py index a58cec3f..06dee725 100644 --- a/patterns/dependency_injection.py +++ b/patterns/dependency_injection.py @@ -2,21 +2,28 @@ # -*- coding : utf-8 -*- """ -Port of the Java example of "Constructor Injection" in +Dependency Injection (DI) is a technique whereby one object supplies the dependencies (services) +to another object (client). +It allows to decouple objects: no need to change client code simply because an object it depends on +needs to be changed to a different one. (Open/Closed principle) + +Port of the Java example of Dependency Injection" in "xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros (ISBN-10: 0131495054, ISBN-13: 978-0131495050) -production code which is untestable: +In the following example `time_provider` (service) is embedded into TimeDisplay (client). +If such service performed an expensive operation you would like to substitute or mock it in tests. class TimeDisplay(object): def __init__(self): - self.time_provider = datetime.datetime + self.time_provider = datetime.datetime.now def get_current_time_as_html_fragment(self): - current_time = self.time_provider.now() + current_time = self.time_provider() current_time_as_html_fragment = "{}".format(current_time) return current_time_as_html_fragment + """ import datetime