Unit testing - suitable for all development?

K

Kylotan

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.

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. In turn, these other
objects rely on others, and on general initialisation procedures. The
end result is that there are very few objects I can truly test in
isolation; I have to initialise at least 80% of the system before I
can even create most of my objects. This ends up meaning that unit
testing isn't really testing a single unit at all, and in fact isn't
much more precise than liberally using asserts in the normal code.

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.

Has anybody else come to similar conclusions?
 
J

John Roth

Kylotan said:
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.

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. In turn, these other
objects rely on others, and on general initialisation procedures. The
end result is that there are very few objects I can truly test in
isolation; I have to initialise at least 80% of the system before I
can even create most of my objects. This ends up meaning that unit
testing isn't really testing a single unit at all, and in fact isn't
much more precise than liberally using asserts in the normal code.

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.

Has anybody else come to similar conclusions?

Coupling is the enemy.

The XP people have a lot of experiance with reducing
coupling. Writing tests first, and insisting that literally
hundreds of unit tests run in a few seconds teaches you
how to write code with a minimum of coupling. Some of
those lessons aren't particularly easy.

I'm very much a fan of creating objects in a state so that
the object invariants pass. I've got two suggestions.

The first is to see if you can redesign using a layered
architecture. That may force you to reduce the number
of objects that any given object depends on.

The second is to investigate mock objects, or other
methods of stubbing out dependencies. While you do
need to run the entire system as a unit, there is no
reason you need to run the real production objects
for all tests.

John Roth
 
R

Roy Smith

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?
 
K

Kylotan

John Roth said:
Coupling is the enemy.

The XP people have a lot of experiance with reducing
coupling. Writing tests first, and insisting that literally
hundreds of unit tests run in a few seconds teaches you
how to write code with a minimum of coupling. Some of
those lessons aren't particularly easy.

Yeah, that's what I'm guessing. I'm at the stage where I have to
wonder whether forcing myself to make the paradigm shift will be worth
the effort or not.
The first is to see if you can redesign using a layered
architecture. That may force you to reduce the number
of objects that any given object depends on.

What do you mean by layered in this context? Are you talking things
like the Bridge design pattern?
The second is to investigate mock objects, or other
methods of stubbing out dependencies. While you do
need to run the entire system as a unit, there is no
reason you need to run the real production objects
for all tests.

That seems like a nice idea, but I've been developing this system as
an evolving prototype. I add new objects and classes as I discover I
need them. Therefore there's almost no functionality there at all that
isn't needed for the system as it stands.

Here's an example of my current system: the Creature class requires a
Race class and a World class to be instantiated, as the Creature's
__init__ function relies on calling methods of these objects (not just
on their presence). Sure, technically I could create dummy objects to
pass in that have do-nothing functions, but then I break most of
Creature's accessors, which are implemented in terms of delegating to
the other objects. In turn this would make the tests meaningless.

I guess what I'm trying to say is that I create systems that arise
from the grouping of classes, and that taking the class out of context
makes it useless. I don't have many things that are low-level enough
to make sense when tested in isolation. (This contrasts heavily to my
development in C++, for example.) And the classes have very little
internal behaviour; it's all done in terms of other classes.

None of this makes testing any harder or less useful in catching bugs;
it just means that it ends up being an almost straight duplication of
my normal code, with the input handling stripped out! It hardly seems
worth the effort.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top