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:
As developers, we need the ability to exchange production objects with mocks and stubs.
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.
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.
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
.
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.
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.
class StubDataAccess(object): def hits(self): return 10.0 def edits(self, page_name): return 2.0
Notice how our stub version returns canned values that are easy to test against.
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.
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.
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.
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.
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.
This shifts the dependency between X and Y over to the container, reducing the coupling.
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.
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.
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.
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:
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
.
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.
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.
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
.
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.
With this adjustment to WikiService
and WikiProductionAppConfig
, we can now code an alternative way of setting up our test case.
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.
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:
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
.