Test-driven development (was: Tracing the execution of scripts?)

Ben Finney bignose+hates-spam at benfinney.id.au
Fri Oct 27 20:51:41 EDT 2006


"Michael B. Trausch" <mike$#at^&nospam!%trauschus at bag.python.org> writes:

> Ben Finney wrote:
> > Jean-Paul Calderone <exarkun at divmod.com> writes:
> > 
> >> 1) Write unit tests for your code.  Keep writing unit tests until
> >> you have some that _don't pass_.  Then fix your code so that they
> >> do.  When you do further development, write the tests first, then
> >> implement the code that makes them pass.
> > 
> > Hear hear.
> > 
> > Be advised, though, that attempting to apply unit tests to code
> > that wasn't designed with testing in mind may very quickly reveal
> > a poor design. [0] If you can't easily test pieces of the code
> > independently, you probably haven't written those pieces to be
> > loosely coupled and well-defined.
> >
>
> I will whole-heartedly admit that my code is probably poorly
> designed.  *shrug* Though, I *do* try to write everything in such a
> way as to be able to easily re-use it later.

The trick is to write pieces that are small and loosely-coupled, so
that small pieces of code (not just entire modules) can be re-used
without being re-written.

Test-driven development helps with this, because a small,
loosely-coupled piece of code is easy to test, so you will gravitate
toward that style.

> Besides, I am lazy... reuse certainly serves my needs there!  :-)

The programming virtues espoused by Larry Wall continue to hold true :-)

> I think I have more to learn on the concept of unit-testing.

> I have never seen a group push the idea so hard.

Writing each test before the functional code is not a new idea, but
Python makes it trivially easy to do, so the excuses for not doing it
are much weaker and easier to ridicule :-)

> I have read about unit testing before, and even written tests (in
> other languages) to test modules of library code that I put together
> once, but that was about it.

Here's an article that goes through the concepts, and happens to use
Python.

    <URL:http://www.onlamp.com/pub/a/python/2004/12/02/tdd_pyunit.html>

> I need to, I think, get more "into" the concept, though.  It isn't
> something that I am able to just whip out and go "Hey, a widget unit
> test!  And it works!" probably because of my own lack of practice.

One thing to note is that UI widgets shouldn't be the main thing
you're testing. You need to write code that can be easily tested
independent of everything else, because that way you can know that it
works and move on to other things quickly, building on it as a
foundation.

UI widgets should be as dumb as possible, because they don't make a
whole lot of sense by themselves except in the dumbest sense. Every
part of your program that actually does something interesting with
data or resources should be utterly independent of any UI
widgets. *That* is the code that you need to get right, so that should
be riddled with tests.

Your widgets should be plugging into code that dumbly passes the data
to the well-tested, UI-independent code that does work with that data;
and the widgest should be getting their data from such UI-independent
outputs. This is one aspect of "loose coupling": the code talks along
a very narrow, well-defined, simple interface to other parts of the
code.

UI code especially is prone to change entirely independent of the
desired functionality. for this reason, it's vital that you *can*
change the functionality and UI code each without needing to change
the other. The same principle applies along any interface between
parts of code that can be expected to change independent of each
other: loosely couple them together, with narrow well-defined
interfaces.

You may have heard of the "Model, View, Controller" pattern. This is a
design pattern that emphasises a loose coupling between the UI (the
"controller") and the data model, by abstracting each to the other via
a simple "view" interface module. That way, each of the Controller and
the Model can be oblivious to the most common changes in the other,
even when those changes make for complex code, because the View they
interact with stays simple and doesn't need to change.

    <URL:http://en.wikipedia.org/wiki/Model-view-controller>

The reason test-driven development is so useful here is that, in order
to write a test for something *before* it exists, you must think about
how that piece of code will interact with the rest of the code. You
must, in other words, design its interface before writing its
code. This is excellent, because it means you're focussing on exactly
the thing that will help to keep your overall design loosely coupled.

Then, after you have a test that interacts with this interface, all
you need to do is write the simplest code to satisfy that
interface. The temptation to write clever, complex code is reduced,
because you're now interested in satisfying the external test, not in
treating this piece of code as an awesome complex machine ignoring the
rest of the code.

Good luck with getting test infected :-)

    <URL:http://c2.com/cgi/wiki?TestInfected>

-- 
 \      "I bought a self learning record to learn Spanish. I turned it |
  `\        on and went to sleep; the record got stuck. The next day I |
_o__)                could only stutter in Spanish."  -- Steven Wright |
Ben Finney




More information about the Python-list mailing list