views:

121

answers:

1

I have a method which calls for a classmethod of another class

def get_interface_params_by_mac(self, host, mac_unified):
        lines = RemoteCommand.remote_command(host, cls.IFCONFIG)

...

class RemoteCommand(object):

    @classmethod
    def remote_command(cls, host, cmd, sh = None):
    ...

I'm going to write a unit test for *get_interface_params_by_mac* method, in which I'd like to change an implementation of *remote_command* (I think it calls stub - fix me if I wrong)

What the right way to do this in Python?

+5  A: 

Your unit-test code (maybe in its setUp method, if this is needed across several test methods and thus qualifies as a fixture) should do:

def fake_command(cls, host, cmd, sh=None):
  pass  # whatever you want in here
self.save_remote_command = somemodule.RemoteCommand.remote_command
somemodule.RemoteCommand.remote_command = classmethod(fake_command)

and then undo this monkey-patching (e.g. in the tearDown method if the patching is done in setUp) by

somemodule.RemoteCommand.remote_command = self.save_remote_command

It's not always necessary to put things back after a test, but it's good general practice.

A more elegant approach would be to design your code for testability via the Dependency Injection (DI) pattern:

def __init__(self, ...):
   ...
   self.remote_command = RemoteCommand.remote_command
   ...

def set_remote_command_function(self, thefunction):
   self.remote_command = thefunction

def get_interface_params_by_mac(self, host, mac_unified):
        lines = self.remote_command(host, cls.IFCONFIG)

DI buys you a lot of flexibility (testability-wise, but also in many other contexts) at very little cost, which makes it one of my favorite design patterns (I'd much rather avoid monkey patching wherever I possibly can). Of course, if you design your code under test to use DI, all you need to do in your test is appropriately prepare that instance by calling the instance's set_remote_command_function with whatever fake-function you want to use!

Alex Martelli
Thanks, Alex. This is exactly what I need. I'd like also to read something about DI. Could you give me a link?
Another question... Do I really need a method set_remote_command_function? As I understand I can write something like this: myobj.remote_command = RemoteCommand.remote_command
@legesh, for DI, see e.g the PDF of a presentation of mine at http://www.aleax.it/yt_pydi.pdf . No, you don't necessarily need a setter-method, direct attribute assignment (possibly through a property if you need to do some book-keeping upon such assignments) can work too.
Alex Martelli