views:

251

answers:

6

In general I want to disable as little code as possible, and I want it to be explicit: I don't want the code being tested to decide whether it's a test or not, I want the test to tell that code "hey, BTW, I'm running a unit test, can you please not make your call to solr, instead can you please stick what you would send to solr in this spot so I can check it". I have my ideas but I don't like any of them, I am hoping that there's a good pythonic way to do this.

A: 

You have two ways to do this is no ,or minimal in the case of DI, modifications to your source code

The cleanest way is using dependency injection, but I don't really like extensive monkeypatching, and there are some things that are non-possible/difficult to do that dependency injection makes easy.

voyager
+7  A: 

You can use Mock objects to intercept the method calls that you do not want to execute. E.g. You have some class A, where you don't want method no() to be called during a test.

class A:
  def do(self):
    print('do')
  def no(self):
    print('no')

A mock object could inherit from A and override no() to do nothing.

class MockA(A):
  def no(self):
    pass

You would then create MockA objects instead of As in your test code. Another way to do mocking would be to have A and MockA implement a common interface say InterfaceA.

There are tons of mocking frameworks available. See StackOverflow: Python mocking frameworks.

In particular see: Google's Python mocking framework.

Pierre-Antoine LaFayette
Subclassing to implement mocking potentially brings along all kinds of things you don't need. Also, your methods don't take the required first argument
Mike Graham
Wow, I'm really digging Michael Foord's Mock. It's beautiful how easy it is to create Mock objects in python. If you've had to do this manually in C++ using interface's and inheritance you know how much of a pain this could be.EDIT: BTW, this answer doesn't really do Mock's correctly, but points the way to what you may be looking for. See [http://www.voidspace.org.uk/python/mock/examples.html#creating-a-mock-from-an-existing-object]
manifest
@Mike Graham, thanks for pointing out the missing argument. I'm not saying subclassing is the best way to do Mocking, I was just giving an example.
Pierre-Antoine LaFayette
A: 

Typically when something like this arises you use Monkey Patching (also called Duck Punching) to achieve the desired results. Check out this link to learn more about Monkey Patching.

In this case, for example, you would overwrite solr to just print the output you are looking for.

Daniel
+4  A: 

Use Michael Foord's Mock in your unit test do this:

from mock import Mock

class Person(object):
    def __init__(self, name):
        super(Person, self).__init__()
        self.name = name

    def say(self, str):
        print "%s says \"%s\"" % (self.name, str)


...

#In your unit test....
#create the class as normal
person = Person("Bob")
#now mock all of person's methods/attributes
person = Mock(spec=person)
#talkto is some function you are testing
talkTo(person)
#make sure the Person class's say method was called
self.assertTrue(person.say.called, "Person wasn't asked to talk")

#make sure the person said "Hello"
args = ("Hello")
keywargs = {}
self.assertEquals(person.say.call_args, (args, keywargs), "Person did not say hello")
manifest
Wow, Mock is awesome! Thanks. In my C++/Java days I used to spend way too much time writing mock classes by hand.
guidoism
Yea, no kidding! I just learned about this stuff reading this question.. definitely adding it to the book of tricks
manifest
A: 

I know it's the typical use case for mock objects, but that's also an old argument... are Mock objects necessary at all or are they evil ?

I'm on the side of those who believe mocks are evil and would try to avoid changing tested code at all. I even believe such need to modify tested code is a code smell...

If you wish to change or intercept an internal function call for testing purpose you could also make this function an explicit external dependency set at instanciation time that would be provided both by your production code and test code. If you do that the problem disappear and you end up with a cleaner interface.

Note that doing that there is not need to change the tested code at all neither internally nor by the test being performed.

kriss
A: 

The big problem that I was having was with the mechanics of the dependency injection. I have now figured that part out.

I need to import the module in the exact same way in both places to successfully inject the new code. For example, if I have the following code that I want to disable:

from foo_service.foo import solr
solr.add(spam)

I can't seem to do this in the in my test runner:

from foo import solr
solr = mock_object

The python interpreter must be treating the modules foo_service.foo and foo as different entries. I changed from foo import solr to the more explicit from foo_service.foo import solr and my mock object was successfully injected.

guidoism