472,096 Members | 1,242 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 472,096 software developers and data experts.

Programmatically replacing an API for another module

I've got a bunch of code that runs under a bunch of unit tests. It'd
be really handy if when testing I could supply replacement
functionality to verify that the right things get called without those
things actually getting called, since frequently those calls take a
long time to execute and do things that, well, I don't really want to
do.

E.g. imagine you've got a routine which takes a filespec, generates a
list of files, and copies those files from one location to another. (I
realize that this example could be multiple functions that should be
tested independently, but please bear with me.) You don't actually
want to do the file copy, but you want to make sure that the file copy
API is appropriately called.

Using win32com, I might then use win32file.CopyFile, but from testing
code I'd like to replace that with a different call, a test function
that I supply. Ideally I could replace the whole module, after
importing the module under test, I could do something like 'win32com =
__dict__', and assuming I had a CopyFile function defined in the test
module, it'd hook right in. However, I'm suspicious that this
wouldn't actually override the win32com seen by the other module that
will actually do the execution (and I don't yet understand Python's
execution model sufficiently).

At present, ModuleTest.py does 'from Module import *' and then makes
calls on Module without the "Module." decoration. Since typically a
file named SomeClass.py will only define the SomeClass class, this is
more of a convenience than anything. If this is a barrier to
replacing APIs, though, well, I can change it.

Any insight is helpful, thanks.
-tom!
Nov 20 '06 #1
10 1580
Tom i think i know what you are talking about. If i do then i have recently
had to deal with something similar.

One approach could be to create a module that acts as an interface between
your code and the api whos functions you wish to use.

The interface would consist of wrapper functions for all of the functions
you wish to use. This interface will only include the wrappers for the api
functions you will actually use.

Now you have created the interface you will need to import the module whose
functions you want to wrap up in the interface.

At this point you can either import a stub or the real thing. The code
snippet would look like this

try:
import stub_api as api
except:
import real_api as api

def get():
return api.get()

......................etc
So when you are testing you make sure that the stub_api module is available
to the interface, the try will succeed and you will call stub functions. If
you do not make the stub_api available then the try will fail, the exception
will be handled by importing the real_api. In either case functions will be
callable by using the api. as you have effectively renamed what is being
imported to api.

The advantages of this are that you only make visible to your code the
fuctions of the api that you are going to use. And you are isolating changes
to code to the interface only.

Ok hope this helps.

Steve

"Tom Plunket" <to***@fancy.orgwrote in message
news:ag********************************@4ax.com...
I've got a bunch of code that runs under a bunch of unit tests. It'd
be really handy if when testing I could supply replacement
functionality to verify that the right things get called without those
things actually getting called, since frequently those calls take a
long time to execute and do things that, well, I don't really want to
do.

E.g. imagine you've got a routine which takes a filespec, generates a
list of files, and copies those files from one location to another. (I
realize that this example could be multiple functions that should be
tested independently, but please bear with me.) You don't actually
want to do the file copy, but you want to make sure that the file copy
API is appropriately called.

Using win32com, I might then use win32file.CopyFile, but from testing
code I'd like to replace that with a different call, a test function
that I supply. Ideally I could replace the whole module, after
importing the module under test, I could do something like 'win32com =
__dict__', and assuming I had a CopyFile function defined in the test
module, it'd hook right in. However, I'm suspicious that this
wouldn't actually override the win32com seen by the other module that
will actually do the execution (and I don't yet understand Python's
execution model sufficiently).

At present, ModuleTest.py does 'from Module import *' and then makes
calls on Module without the "Module." decoration. Since typically a
file named SomeClass.py will only define the SomeClass class, this is
more of a convenience than anything. If this is a barrier to
replacing APIs, though, well, I can change it.

Any insight is helpful, thanks.
-tom!

Nov 20 '06 #2
Tom Plunket wrote:
I've got a bunch of code that runs under a bunch of unit tests. It'd
be really handy if when testing I could supply replacement
functionality to verify that the right things get called without those
things actually getting called, since frequently those calls take a
long time to execute and do things that, well, I don't really want to
do.

E.g. imagine you've got a routine which takes a filespec, generates a
list of files, and copies those files from one location to another. (I
realize that this example could be multiple functions that should be
tested independently, but please bear with me.) You don't actually
want to do the file copy, but you want to make sure that the file copy
API is appropriately called.

Using win32com, I might then use win32file.CopyFile, but from testing
code I'd like to replace that with a different call, a test function
that I supply. Ideally I could replace the whole module, after
importing the module under test, I could do something like 'win32com =
__dict__', and assuming I had a CopyFile function defined in the test
module, it'd hook right in. However, I'm suspicious that this
wouldn't actually override the win32com seen by the other module that
will actually do the execution (and I don't yet understand Python's
execution model sufficiently).

At present, ModuleTest.py does 'from Module import *' and then makes
calls on Module without the "Module." decoration. Since typically a
file named SomeClass.py will only define the SomeClass class, this is
more of a convenience than anything. If this is a barrier to
replacing APIs, though, well, I can change it.

Any insight is helpful, thanks.
-tom!
Whatever points to something in Python is what is called.

example

def abc(a, b):
z=a+b
return z

def efg(a, b):
z=a*b
return z
f=abc
print f(1,2)
f=efg
print f(1,2)

As long as you implement the full API, you can replace functions
with others quite easily. Note: this also works with class methods.

Observation: IMHO it is going to be really hard to keep your functional
shims up to date as you change your REAL functions/methods. You also
are testing different code than you are executing which doesn't make
the unit tests nearly as valuable.

-Larry
Nov 20 '06 #3
Larry Bates wrote:
Whatever points to something in Python is what is called.
Right.
f=abc
print f(1,2)
f=efg
print f(1,2)
Sure. I realized after posting that what I really wanted to ask was
how to replace an imported module. Steve's earlier response is akin
to what I'm looking for, although with far too much work. ;)
As long as you implement the full API, you can replace functions
with others quite easily. Note: this also works with class methods.
Right, I can replace a function at a time:

import os

def my_join(*args):
return '/'.join(*args)

os.path.join = my_join

....but I'm wondering if there's a straight-forward way to inject the
local list of functions into another module.

What I ran into after posting is that I don't know how to get the
__dict__ for the module's scope; is it available? I.e. I can do
'print os.path.__dict__', but 'print __dict__' at module scope results
in a NameError since __dict__ is undefined at that scope. However, if
I could do 'os.path = local_module' somehow, I'd be getting basically
what I want. Maybe I could just have a my_os_path.py, and in my test
code import it as os.path, and then the other code would see that
os.path is already loaded and not bother doing so again? Haven't
tried it, am interested in the answer, but I do have enough to work
with for now... Replacing a module in this way also ensures that I'm
only calling the parts of the API that I intend to call, which is a
handy side-effect.
Observation: IMHO it is going to be really hard to keep your functional
shims up to date as you change your REAL functions/methods. You also
are testing different code than you are executing which doesn't make
the unit tests nearly as valuable.
Depends on what all I'm mocking. If I'm mocking an expensive service
across the internet that is time consuming to connect to, or even
mocking a well-defined system API call that is similarly time
consuming to call, then I can stub something in that doesn't do
anything but validate the arguments. Additionally, I can "document"
the caveats and possibly-errant behavior with my mock, and
subsequently get automatic testing that I'm dealing with API bugs
properly.

These aren't functional shims, which would imply that they sit in
between two other bits of interesting code under my control. They're
functional leaves, which are the end point of the operational chain.
E.g. I queue up all of the commands that I'm sending to the database
and verify that they're what I intended to send, and I accept that the
database will interpret those commands as it should.

Thanks for the feedback, regardless,
-tom!
Nov 21 '06 #4
At Monday 20/11/2006 21:52, Tom Plunket wrote:
>What I ran into after posting is that I don't know how to get the
__dict__ for the module's scope; is it available? I.e. I can do
'print os.path.__dict__', but 'print __dict__' at module scope results
in a NameError since __dict__ is undefined at that scope. However, if
I could do 'os.path = local_module' somehow, I'd be getting basically
what I want. Maybe I could just have a my_os_path.py, and in my test
code import it as os.path, and then the other code would see that
os.path is already loaded and not bother doing so again? Haven't
tried it, am interested in the answer, but I do have enough to work
with for now... Replacing a module in this way also ensures that I'm
only calling the parts of the API that I intend to call, which is a
handy side-effect.
You can do this:
import my_os_path
os.path = my_os_path

That shows also that you can use the module as its own namespace. No
need to use __dict__ explicitely. dir(module) gives you the names
defined in the module. From inside the module self, use globals().
--
Gabriel Genellina
Softlab SRL

__________________________________________________
Correo Yahoo!
Espacio para todos tus mensajes, antivirus y antispam ˇgratis!
ˇAbrí tu cuenta ya! - http://correo.yahoo.com.ar
Nov 21 '06 #5
Tom Plunket wrote:
I've got a bunch of code that runs under a bunch of unit tests. It'd
be really handy if when testing I could supply replacement
functionality to verify that the right things get called without those
things actually getting called, since frequently those calls take a
long time to execute and do things that, well, I don't really want to
do.
Congratulation, you just rediscovered the MockObject.

(snip)

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in 'o****@xiludom.gro'.split('@')])"
Nov 21 '06 #6
Depends on what all I'm mocking. If I'm mocking an expensive service
across the internet that is time consuming to connect to, or even
mocking a well-defined system API call that is similarly time
consuming to call, then I can stub something in that doesn't do
anything but validate the arguments. Additionally, I can "document"
the caveats and possibly-errant behavior with my mock, and
subsequently get automatic testing that I'm dealing with API bugs
properly.
When I find myself doing what you describe, I often introduce
a "testing" mode switch into the equation. It is normally a
keyword argument to functions that I then interrogate in the
function. If I'm running in testing mode, I execute the less
"expensive" testing code. If I'm not in test mode, I don't.
There may be better ways, but this has worked well for me.

-Larry
Nov 21 '06 #7
Bruno Desthuilliers wrote:
I've got a bunch of code that runs under a bunch of unit tests. It'd
be really handy if when testing I could supply replacement
functionality to verify that the right things get called without those
things actually getting called, since frequently those calls take a
long time to execute and do things that, well, I don't really want to
do.

Congratulation, you just rediscovered the MockObject.
How do I rediscover something that I'm explicitly implementing? I had
set out specifically to mock an API, there was no rediscovery going
on.

That does allow me to discover via Google the Python Mock Module,
though, which may well do what I want:

from TestedModule import *

my_win32file = Mock({
'CopyFile': 'CopyFile called'
}

win32file = my_win32file

....perhaps.

I'm just a bit loathe to download and install more stuff when
something simpler appears to be near-at-hand. ...especially
considering the page describing this module doesn't offer any download
links! http://python-mock.sourceforge.net/
-tom!
Nov 22 '06 #8
On Tue, Nov 21, 2006 at 05:00:32PM -0800, Tom Plunket wrote:
Bruno Desthuilliers wrote:
I've got a bunch of code that runs under a bunch of unit tests. It'd
be really handy if when testing I could supply replacement
functionality to verify that the right things get called without those
things actually getting called, since frequently those calls take a
long time to execute and do things that, well, I don't really want to
do.
Congratulation, you just rediscovered the MockObject.

How do I rediscover something that I'm explicitly implementing? I had
set out specifically to mock an API, there was no rediscovery going
on.

That does allow me to discover via Google the Python Mock Module,
though, which may well do what I want:

from TestedModule import *

my_win32file = Mock({
'CopyFile': 'CopyFile called'
}

win32file = my_win32file

...perhaps.

I'm just a bit loathe to download and install more stuff when
something simpler appears to be near-at-hand. ...especially
considering the page describing this module doesn't offer any download
links! http://python-mock.sourceforge.net/
How about mini-mock:
http://blog.ianbicking.org/minimock.html
>

-tom!
--
http://mail.python.org/mailman/listinfo/python-list
Nov 22 '06 #9
I'm just a bit loathe to download and install more stuff when
something simpler appears to be near-at-hand. ...especially
considering the page describing this module doesn't offer any download
links! http://python-mock.sourceforge.net/
How about mini-mock:
http://blog.ianbicking.org/minimock.html
Oh yeah, and the download link for python-mock is:
http://sourceforge.net/project/showf...roup_id=138804
Nov 22 '06 #10
Chris Lambacher wrote:
I'm just a bit loathe to download and install more stuff when
something simpler appears to be near-at-hand. ...especially
considering the page describing this module doesn't offer any download
links! http://python-mock.sourceforge.net/

Oh yeah, and the download link for python-mock is:
http://sourceforge.net/project/showf...roup_id=138804
Heh, yeah I'm sure that's what I got when I went to sf.net and typed
in the project name, then submitted a bug for the homepage that it
doesn't offer any links to find the download. ;)

-tom!
Nov 22 '06 #11

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

5 posts views Thread by Carlo Marchesoni | last post: by
3 posts views Thread by Codex Twin | last post: by
1 post views Thread by jcz_ch | last post: by
3 posts views Thread by aine_canby | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.