Post by Brill Pappin[...]
Post by Tunca BergmenAs I said before, I am very new to these practices. One thing I
wonder about the philosophy of XP is, when we say the term "test
driven development", do we mean that we should change our
programming logic in order to make our testing logic simpler, even
when it has negative effects on the programming logic?
The sort answer is yes, change your "programming logic" but don't do it
*just* so you can test. If you do the simplest thing possible in your
code, your test should also be simple and it follows that if you make
the test simple, you code should follow suit (only doing exactly what is
required to pass the tests).
Actually (I can't stress enough) writing the test doesn't have negative
effects on the actual code under test (or shouldn't if your being good
about how you write your tests)... I can't name one single case where I
wouldn't want the test in place... but I can give you a multitude where
not writing the tests has a negative effect.
That said, it does mean you have very *different* code that what you
would have written had you not used TDD and I find that a lot of folks
new to TDD think of those differences as negative.
There are a whole slew of theories and practices that work with TDD
about why that new code structure is actually better, but I don't think
I could sum it all up in a single message or even sell you on the fact
(I'm just not that good a salesman). However I really like how this
fellow described it on his blog (sorry, can't find his name)
http://www.williamcaputo.com/archives/000019.html
IMO - As a rule well written tests will guide you down the path to agile
code all by themselves and is the cornerstone of Agile development
practices.
- Brill Pappin
I went back and re-read Bill Caputo's blog entry and have to say that
overall, I agree. What concerns me, and I still struggle with this, is
how the community seems to consistently sidestep the issue of whether
Dependency Inversion is really being applied in the designs produced by
TDD. Bill says yes. But if I reconstruct what I think his pseudocode
says, his class looks like this:
public class CustomerDAO {
public void updateCustomer(Customer data) {
updateCustomerImpl(data, new RealDatabase("SomeSystem"));
}
// this method is for test purposes only
public void updateCustomerImpl(Customer data, IDatabase db) {
....
}
}
Frankly, I have problems with this on multiple levels. First, you've
introduced a method in the public API of the class that is only there
for testing. I mean, updateCustomerImpl it is only there for the test to
execute whereas the production code is expected to execute
updateCustomer. You might as well have named the method
"thisIsATestMethodForInjectingAMockDatabase" for god's sake. Nobody but
the test will use it. And as such I think you've violated one of Bill
Riel's heuristics of OOD that states you shouldn't clutter the public
API of a class with things that users of that class are not able to use
or are not interested in using.
Second, it is my opinion that this doesn't really decouple the
CustomerDAO from the RealDatabase. Yes, indeed, it provides a mechanism
for injecting a mock at test time. But I think what's really happened is
that we've decided to merely pay lip service to DIP. If it were
otherwise, shouldn't I be able to reuse CustomerDAO without dragging
along RealDatabase? But obviously I cannot. There is a compile time
dependency.
If I were to rewrite Robert Martin's copy program using this strategy it
would probably look something like this:
public class Copy {
// this method is for test purposes only
public void copy(Reader reader, Writer writer) {
while ((int c = reader.read() != EOF) {
writer.write(c);
}
}
public void copy() {
copy(new KeyboardReader(), new PrinterWriter());
}
}
The copy method that takes a Reader and Writer, the real DIP
implementation, is essentially relegated to a test method. In scenarios
where I choose to put test classes in the same package as my production
classes, I'd probably even choose to make the method default access so
that only classes in the same package or subclasses could access it. Is
this really what we intend to recommend as good design?
Perhaps we would prefer a common alternative:
public class Copy {
public void copy() {
Reader reader = getReader();
Writer writer = getWriter();
while ((int c = reader.read() != EOF) {
writer.write(c);
}
}
// replace me if input does not come from keyboard
protected Reader getReader() {
return new KeyboardReader();
}
// replace me if output does not go to printer
protected Writer getWriter() {
return new PrinterWriter();
}
}
And then subclass Copy in our test and override (replace) the
implementation of getReader and getWriter to return mocks. Not a
particularly favorable alternative IMO. I think Jeff Langr calls this
pattern/idiom "deferred factory method mock".
Yet again we might do this:
public class Copy {
Reader reader;
Writer writer;
public Copy() {
this(new KeyboadReader(), new PrinterWriter());
}
// this constructor is for test purposes only
public Copy(Reader reader, Writer writer) {
this.reader = reader;
this.writer = writer;
}
public void copy() {
while ((int c = reader.read() != EOF) {
writer.write(c);
}
}
}
Same as the first but with dependencies moved to a no-arg constructor.
Jeff Langr calls this "parameterized replacement mock". In this case the
language doesn't support it but the second constructor's intent is
revealed better by the name "CopyConstructorForTestPurposesOnly" because
we don't really intend that a client (other than the test) ever actually
use it. Again, in the right scenario I'd probably make it default access
so clients really /couldn't/ use it. No matter what, I guess I have to
add a comment telling everyone that this method is for test purposes
only? Does anybody else smell that?
So while I agree strongly that tests should influence the design of the
code, I'm left with a decidedly bad test in my mouth (odor in my nose)
when I then hear that the influence is to either add things to the
class's API that only the test is supposed to use or, conversely, add
things that only the production client(s) are supposed to use because
the design that emerged while writing the code test first was, well,
/too/ good?
Jim Cakalic
[Non-text portions of this message have been removed]
To Post a message, send it to: ***@eGroups.com
To Unsubscribe, send a blank message to: extremeprogramming-***@eGroups.com
ad-free courtesy of objectmentor.com
Yahoo! Groups Links
<*> To visit your group on the web, go to:
http://groups.yahoo.com/group/extremeprogramming/
<*> To unsubscribe from this group, send an email to:
extremeprogramming-***@yahoogroups.com
<*> Your use of Yahoo! Groups is subject to:
http://docs.yahoo.com/info/terms/