Unittests and external data....

Steven Taschuk staschuk at telusplanet.net
Thu Mar 20 15:06:46 EST 2003


Quoth mcherm at mcherm.com:
> Michael Chermside writes:
>   [...]
> > I don't particularly like having the factory functions behave
> > differently under unittesting, but that's the best solution
> > I've come up with.
> 
> Steven Taschuk replies:
> > Why not just pass in the factory function as a parameter to the
> > code that needs it (with a default value, naturally)?  The testing
> > code can then just pass in its own mock version.
> 
> Good question. I usually wind up having things like the the core
> factory for accessing an external service as system globals
> (whatever that means in the language I'm using). So, for instance,
> I don't have to pass around a SQLFactory object to every function
> that might call a function that might need to access the database.
  [...]

Sounds like a refactoring opportunity!

That's a very pat answer to what I readily admit is a real
problem.  Without knowing more about the system in question, it's
hard to be more specific, or even to be sure there's a
satisfactory refactoring available.  However:

In my experience the annoyance of passing around a database
connection factory arises chiefly in the following circumstance:
function X does its work by accessing the database; function Y
does its work by calling X, but does not really care that the
database is involved.  If X requires a connection factory
parameter, then Y needs one too, which increases coupling between
X and Y.  It then, e.g., becomes difficult to make Y work with
alternative versions of X that, say, do not use the database or
use different kinds of database.

If this is an important consideration, then perhaps the X to use
should be a parameter to Y.  That is, I prefer to have Y take X as
a parameter, rather than the connection factory.  Rather than
doing, say,
	def X(factory):
		... do something with factory ...
	def Y(factory):
		... do something with X(factory) ...
	if __name__ == '__main__':
		... invoke Y(factory) ...
I prefer, say,
	class X(object):
		def __init__(self, factory):
			self.factory = factory
		def doX(self):
			... do something with self.factory
	def Y(x):
		... do something with x.doX() ...
	if __name__ == '__main__':
		... invoke Y(X(factory)) ...
or the same solution in disguise, using functions for
encapsulation instead of objects:
	def makeX(factory):
		def X():
			... do something with factory ...
		return X
	def Y(x):
		... do something with x() ...
	if __name__ == '__main__':
		... invoke Y(makeX(factory)) ...
Under such approaches, X and Y are pleasantly loosely coupled.
The business of the top level of the application becomes
determining (by reading configuration files or what have you) the
appropriate objects to instantiate, hooking them together, and
letting fly.

Such implementations take more work than ones using globals, but
they improve modularity.  Ease of unit testing is just one example
of that modularity in action.

(If, on the other hand, X and Y are intrinsically tightly coupled,
perhaps they should be methods of the same object, and the
connection factory an attribute of that object.  This can be
better than using globals.)

-- 
Steven Taschuk                               staschuk at telusplanet.net
"What I find most baffling about that song is that it was not a hit."
                                          -- Tony Dylan Davis (CKUA)





More information about the Python-list mailing list