Unit testing - suitable for all development?
Roy Smith
roy at panix.com
Sun Mar 7 12:50:21 EST 2004
kylotan at hotmail.com (Kylotan) wrote:
> Today I tried to implement some sort of unit testing into my program
> for the first time, and must admit to being a little disillusioned
> with the process. Mainly, my issue is that in my program, the classes
> are so tightly coupled that testing in isolation is next to
> impossible.
Yup, this is often a problem when you first start unit testing. Tightly
coupled classes are difficult to test.
Tightly coupled classes cause lots of problems too. They make it hard
to understand how your system works, and they make it hard to change out
components later. One of the things that unit testing does is it forces
you to write classes so they are easily testable, which usually means
not a lot of inter-class dependencies.
> The main problem stems from the fact that I try to ensure that all my
> objects are created in a working state. This often means passing
> various other objects to the __init__ function.
That's fine. Let's imagine you've got:
class Foo:
def __init__ (self, language, timeZone, hairColor):
self.language = language
self.timeZone = timeZone
self.hairColor = hairColor
def getBreakfastFood (self):
do a lot of stuff involving language, etc
return (food)
class Bar:
def __init__ (self, foo):
self.foo = foo
On the surface, it looks like you can't test Bar without having a
working Foo. But maybe you can provide a stub implementation of Foo
which does just enough to allow Bar to be tested:
class FooStub:
def getBreakfastFood (self):
return "spam"
then you can do in your TestCase class, something like:
def setUp (self):
foo = FooStub ()
self.bar = Bar (foo)
and you're all set. This assumes that there's something about Bar's
behavior which depends on foo having a getBreakfastFood () method which
actually works. If the classes were less coupled, Bar might do nothing
with the foo it was passed other than treat it as opaque data to be
retrieve by a getFoo() method. In which case, your stub class could be
even simplier:
class FooStub:
pass
and you might even be able to get away with no FooStub class at all, but
simply instantiating your Bar test object with a constant:
def setUp (self):
self.bar = Bar ("foo")
This is where the dynamic nature of Python really shines. Something
like C++ or Java would make you jump through a lot more hoops to make
sure you instantiated your Bar with a valid Foo. But the basic
principle is the same in any OOPL; the more tighly coupled your classes
are, the more difficult it is to test, maintain, and understand the
system.
> One way out of this would be to reduce coupling. This would allow me
> to test objects in relative isolation, but it would increase the
> amount of explicit coupling code that I'd have to execute normally.
> This extra code then becomes a potential source of new bugs.
One way or another, your classes need to interact, and the code that
implements those interactions needs to exist (and thus needs to be
tested). The question is, where do you put that code? Do you bury it
inside the classes, making it difficult to test both the underlying
classes and their interactions, or do you factor it out to someplace
where you can test each piece in isolation?
More information about the Python-list
mailing list