tags:

views:

475

answers:

2

I have code that looks like this:

import xmlrpclib

class Base(object):
    def __init__(self, server):
        self.conn = xmlrpclib.ServerProxy(server)

    def foo(self):
        return self.conn.do_something()

class Derived(Base):
    def foo(self):
        if Base.foo():
            return self.conn.do_something_else()

How should I use mocking to test the behavior of the Derived class? I don't want to assume that whatever the XML-RPC connection talks to will actually exist, but I feel like mocking the xmlrpclib module requires too much knowledge of the implementation of the Base class (which I have other tests for).

Or, I guess, should I even use mocking to test this? If not, how would you test it?

+3  A: 

You could create a fake ServerProxy class, and substitute that for testing.

Something like this:

class FakeServerProxy(object):
    def __init__(self, server):
        self.server = server
    def do_something(self):
        pass
    def do_something_else(self):
        pass

def test_derived():
    xmlrpclib.ServerProxy = FakeServerProxy
    derived = Derived(None)
    derived.foo()
Ryan Ginstrom
But then I need to know that the `Base` class uses xmlrpclib.I'd like to try to avoid that, since it seems wrong that if the base class's implementation changes that I'd have to change the tests for the derived class, but not the actual implementation of the derived class.
Zach Hirsch
Derived is tightly coupled to Base, so you kind of have to go poking around in Base's internals (without resorting to voodoo). How about making Base a member rather than an ancestor, and faking Base?
Ryan Ginstrom
I guess that works in Python, because of duck typing, but it isn't a very unsatisfying solution to my C++ side, since it doesn't allow for the substitution principle.
Zach Hirsch
@Zach Hirsch: all subclass testing is also a test of the superclass. You can't treat Derived as if it's unrelated to Base. Your tests for Derived will either be a subclass of your tests for Base, or they'll be copy-and-paste clones.
S.Lott
+3  A: 

With some trivial refactoring (call to do_something_else is extracted), you can test Derived.foo logic without needing to "know" about XMLRPC.

import xmlrpclib

class Base(object):
    def __init__(self, server):
        self.conn = xmlrpclib.ServerProxy(server)

    def foo(self):
        return self.conn.do_something()

class Derived(Base):
    def foo(self):
        if Base.foo(self):
            return self._bar()
    def _bar(self):
        # moved to its own method so that you
        # you can stub it out to avoid any XMLRPCs
        # actually taking place.
        return self.conn.do_something_else()

import mox

d = Derived('http://deep-thought/unanswered/tagged/life+universe+everything')

m = mox.Mox()
m.StubOutWithMock(Base, 'foo')
m.StubOutWithMock(d, '_bar')
Base.foo(d).AndReturn(True)
d._bar() # Will be called becase Boo.foo returns True.
m.ReplayAll()

d.foo()

m.UnsetStubs()
m.VerifyAll()

Alternatively or even preferably, you may extract the call to do_something_else into bar method on Base. Which makes sense, if we agree that Base encapsulates all your XMLRPC actions.

The example uses pymox mocking library, but the gist of it stays the same regardless of your mocking style.

Pavel Repin