Chapter 2. The Heart of Spring Python—Inversion of Control

Many software teams seek ways to improve productivity without sacrificing quality. This has led to an increase in automated testing and, in turn, sparked an interest in making applications more testable. Many automated test frameworks promote isolating objects by swapping collaborating objects with test doubles to provide canned responses. This generates a need for developers to plugin test doubles with as little change as possible.

On some occasions, managers have trimmed budgets by cutting back on development and integration hardware. This leaves developers with the quandary of having to develop on a smaller footprint than their production environment. Software developers need the ability to switch between different test configurations and deployment scenarios.

Software products sold to customers often need the ability to make flexible adjustments on site, whether its through a company representative or from the customer.

In this chapter, we will explore how Spring Python's Inversion of Control (IoC) container meets all these needs by making it possible to move the creation of critical objects into a container definition, allowing flexible adjustment without impacting the core business code.

This chapter will cover:

  • How IoC containers make it easy to isolate objects under test in order to work with automated testing.
  • A detailed explanation of IoC with high level diagrams. We will learn how Dependency Injection is a type of IoC and uses the Hollywood principle of Don't call us, we'll call you!
  • Spring Python's IoC container, which handles dependency resolution, setting properties, and lazy initialiation. It also provides extension points for developers to utilize as needed.
  • Spring Python's answer to the community debate of IoC in dynamic languages.
  • How mixing Python and Java components together is easy, can be done many ways, and provides a good way for developers to choose what best fits their needs.

Swapping production code with test doubles

As developers, we need the ability to exchange production objects with mocks and stubs.

Note

Mocks and stubs are two ways of generating canned responses used to test our code. Mocks are used to generate canned method calls. Stubs are used to generate canned data. Both of these are useful tools to simulate external components our code must work with. For more details, see http://martinfowler.com/articles/mocksArentStubs.html

In this example, we are going to explore how an IoC container makes it easy to exchange production objects with mock instances. We start by developing a simple service along with some automated testing for a wiki engine.

  1. First of all, let's define a simple service for our wiki engine. The following WikiService has a function that looks into a MySQL database and returns the number of hits for a page as well as the ratio of reads per edit.
    class WikiService(object):
    def __init_(self):
    self.data_access = MySqlDataAccess()
    def statistics(self, page_name):
    """Return tuple containg (num hits, hits per edit)"""
    hits = self.data_access.hits(page_name)
    return (hits, hits / len(self.data_access.edits(page_name)))
    

    In this situation, WikiService directly defines data_access to use MySqlDataAccess. The statistics method calls into MySqlDataAccess to fetch some information, and returns a tuple containing the number of hits against the page as well as the ratio of reads per edit.

    Note

    It is important to point out that our current version of WikiService has a strong dependency on MySQL as implied by directly creating an instance of MySqlDataAccess.

  2. Next, we will write some startup code used to run our wiki web server.
    if __name__ == "__main__":
    service = WikiService()
    WikiWebApp(service).run()
    

    The startup code creates an instance of WikiService, which in turn creates an instance of MySqlDataAccess. We create an instance of our main web application component, WikiWebApp, and start it up, giving it a handle on our service.

    Note

    We have not defined WikiWebApp or MySqlDataAccess. Instead, we will be focusing on the functionality WikiService provides, and on how to isolate and test it.

    Let's look more closely at testing the code we've written. A good test case would involve exercising statistics. Considering that it uses hits and edits from MySqlDataAccess, it is directly dependent on connecting to a live MySQL database. One option for testing would be pre-loading the database with fi xed content. However, the cost of set up and tear down can quickly scale out of control. If you have multiple developers on your team, you also don't want the contention where one developer is setting up while another is tearing down the same tables.

  3. Since we do not want to continuously setup and tear down live database tables, we are going to code a stubbed out version of the data access object instead.
    class StubDataAccess(object):
    def hits(self):
    return 10.0
    def edits(self, page_name):
    return 2.0
    

    Note

    Notice how our stub version returns canned values that are easy to test against.

  4. Now let's write a test case that exercises our WikiService. In order to replace the data_access attribute with a test double, the test case must directly override the attribute.
    class WikiServiceTestCase(unittest.TestCase):
    def testHittingWikiService(self):
    service = WikiService()
    service.data_access = StubDataAccess()
    results = service.statistics("stub page")
    self.assertEquals(10.0, results[0])
    self.assertEquals(5.0, results[1])
    

This solution nicely removes the need to deal with the MySQL database by plugging in a stubbed out version of our data access layer. These dependencies are depicted in the following diagram.

Swapping production code with test doubles

WikiServiceTestCase depends on WikiService and StubDataAccess, as well as an inherited dependency to MySqlDataAccess. Any change to any of these dependencies could impact our test code.

If we build a huge test suite involving hundreds of test methods, all using this pattern of instantiating WikiService and then overriding data_access with a test double, we have set ourselves up with a big risk. For example, WikiService or StubDataAccess could have some new initializing arguments added. If we later need to change something about this pattern of creating our testable WikiService, we may have to update every test method! We would need to make every change right. This is where Spring Python's Inversion of Control can help.

More about Inversion of Control

Before diving into our solution, let's look a little deeper into the meaning of Inversion of Control.

Inversion of Control is a paradigm where we alter the way we create objects. In simple object creation scenarios, if we have modules X and Y, and X needs an instance of Y, we would let X directly create it. This introduces a direct dependency between X and Y, as shown in the following diagram.

More about Inversion of Control

It's dependent because any changes to Y may impact X and incur required updates. This dependency isn't just between X and Y. X is now dependent on Y and all of Y's dependencies (as shown below). Any updates to Y or any of its dependencies run the risk of impacting X.

More about Inversion of Control

With Inversion of Control, we break this potential risk of impacts from Y and its dependencies to X by delegating creation of instances of Y to a separate container, as shown below.

More about Inversion of Control

This shifts the dependency between X and Y over to the container, reducing the coupling.

Note

It's important to note that the dependency hasn't been entirely eliminated. Because X is still given an instance of Y, there is still some dependency on its API, but the fact that Y and none of its dependencies have to be imported into X makes X a smaller, more manageable block of code.

We saw in our wiki engine code how WikiService was dependent on MySqlDataAccess. MySqlDataAccess is dependent on MySQLdb, a python library for communicating with MySQL. Using the container and coding against a well defined interface opens up the opportunity to change what version of data access is being injected into WikiService.

As we continue our example in the next section, we'll see how IoC can help us make management of test code easier, reducing long term maintenance costs.

Adding Inversion of Control to our application

We already took the first step in reducing maintenance costs by eliminating our dependency on MySQL by using a test stub. However the mechanism we used incurred a great risk due to violation of the DRY (Don't Repeat Yourself) principle.

For our current problem, we want Spring Python to manage the creation of WikiService in a way that allows us to make changes in one place, so we don't have to edit every test method. To do this, we will define an IoC container and let it handle creating the objects for us. The following code shows a simple container definition.

  1. First of all, let's create an IoC container using Spring Python and have it create our instance of WikiService.
    from springpython.config import PythonConfig
    from springpython.config import Object
    class WikiProductionAppConfig(PythonConfig):
    def __init__(self):
    super(WikiProductionAppConfig, self).__init__()
    @Object
    def wiki_service(self):
    return WikiService()
    

    You can spot the objects defi ned in the container by noting Spring Python's @Object decorator.

  2. Now we want to change our startup code so that it uses the container instead of creating WikiService object directly.
    if __name__ == "__main__":
    from springpython.context import ApplicationContext
    container = ApplicationContext(WikiProductionAppConfig())
    service = container.get_object("wiki_service")
    WikiWebApp(service).run()
    

    In this version of our wiki web application, the service object was obtained by asking the container for wiki_service. Spring Python dispatches this request to WikiProductionAppConfig where it invokes the wiki_service() method. It is now up to the container to create an instance of WikiService and return it back to us.

    The dependencies are shown in the following diagram:

    Adding Inversion of Control to our application

    At this intermediate stage, please note that WikiService is still dependent on MySqlDataAccess. We have only modifi ed how our application gets a copy of WikiService. In later steps, we will completely remove this dependency to MySqlDataAccess.

  3. Let's see what our test case looks like when we use the IoC container to retrieve WikiService.
    from springpython.context import ApplicationContext
    class WikiServiceTestCase(unittest.TestCase):
    def testHittingWikiService(self):
    container = ApplicationContext(WikiProductionAppConfig())
    service = container.get_object("wiki_service")
    service.data_access = StubDataAccess()
    results = service.statistics("stub page")
    self.assertEquals(10.0, results[0])
    self.assertEquals(5.0, results[1])
    

With this change, we are now getting WikiService from the container, and then overriding it with the StubDataAccess. You may wonder "what was the point of that?" The key point is that we shifted creation of our testable wiki_service object to the container. To complete the transition, we need to remove all dependency of MySqlDataAccess from WikiService. Before we do that, let's discuss Dependency Injection.

Dependency Injection a.k.a. the Hollywood principle

So far, we have managed to delegate creation of our WikiService object to Spring Python's Inversion of Control container. However, WikiService still has a hard-coded dependency to MySqlDataAccess.

The nature of IoC is to push object creation into a 3rd party location. Up to this point, we have been using the term Inversion of Control. The way that Spring Python implements IoC, is through the mechanism of Dependency Injection. Dependency Injection, or DI, is where dependencies are pushed into objects through either initialization code or by letting the container directly assign attributes. This is sometimes described by the Hollywood cliché of Don't call us, we'll call you. It means that the object which needs a certain dependency shouldn't make it directly, but instead wait on the external container to provide it when needed.

Tip

Because Spring Python only using Dependency Injection, the terms, Inversion of Control, IoC, Dependency Injection, and DI may be used interchangeably throughout this book to communicate the same idea.

The following version of WikiService shows a complete removal of dependence on MySqlDataAccess.

class WikiService(object):
def __init__(self, data_access):
self.data_access = data_access
def statistics(self, page_name):
"""Return tuple containing (num hits, hits per edit)"""
hits = self.data_access.hits(page_name)
return (hits, hits / len(self.data_access.edits(page_name)))

With this altered version of WikiService, it is now up to WikiService's creator to provide the concrete instance of data_access. The corresponding change to make to our IoC container looks like this.

from springpython.config import PythonConfig
from springpython.config import Object
class WikiProductionAppConfig(PythonConfig):
def __init__(self):
super(WikiProductionAppConfig, self).__init__()
@Object
def data_access(self):
return MySqlDataAccess()
@Object
def wiki_service(self):
return WikiService(self.data_access())

We have added a definition for MySqlDataAccess so that we can inject this into the instance of WikiService when it's created. Our dependency diagram now shows a complete break of dependency between WikiService and MySqlDataAccess.

Dependency Injection a.k.a. the Hollywood principle

As described earlier, this is akin to the Hollywood mantra Don't call us, we'll call you. It opens up our code to the possibility of having any variation injected, giving us greater flexibility, without having to rewrite other parts of the system.

Adding Inversion of Control to our test

With this adjustment to WikiService and WikiProductionAppConfig, we can now code an alternative way of setting up our test case.

  1. First, we subclass the production container configuration to create a test version. Then we override the data_access object so that it returns our stub alternative.
    class WikiTestAppConfig(WikiProductionAppConfig):
    def __init__(self):
    super(WikiTestAppConfig, self).__init__()
    @Object
    def data_access(self):
    return StubDataAccess()
    

    Using this technique, we inherit all the production definitions. Then we simply override the parts needed for our test situation. In this case, we return a slightly modifi ed version of WikiService that is suitable for our testing needs.

  2. Now, we can alter our test suite to fetch WikiService just like the production main code, except using our alternative container.
    from springpython.context import ApplicationContext
    class WikiServiceTestCase(unittest.TestCase):
    def testHittingWikiService(self):
    container = ApplicationContext(WikiTestAppConfig())
    service = container.get_object("wiki_service")
    results = service.statistics("stub page")
    self.assertEquals(10.0, results[0])
    self.assertEquals(5.0, results[1])
    

The dependencies (or lack of!) between WikiService and StubDataAccess are shown in following diagram:

Adding Inversion of Control to our test

This makes it easy to plug in our alternative StubDataAccess. An important thing to note is how our test code is no longer overriding the data_access attribute, or doing any other special steps when creating WikiService. Instead, this creation logic has been totally turned over to the IoC container. We can now easily write as many tests as we want with no risk of changes to WikiService or data_access, provided we continue to rely on the IoC container to handle object creation. We just visit the blueprints of WikiTestAppConfig to make any future alterations.

In conclusion of this example, the following diagram shows our current objects as well as potential enhancement to our system. In this possible situation, we have defined multiple data access components. To find out which one is being injected into WikiService, we simply look at the relevant container's definition, whether its production or a particular test scenario. Our current system may use MySQL, but if a new customer wanted to use PostGreSQL, we simply create another variation of our container, and inject the alternate data access object. The same can be said for Sqlite. And all of this can be done with no impact to WikiService.

Adding Inversion of Control to our test
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.171.125