Frank published a paper with an excellent solution for
the code examples (to mention only one point).
http://www.frankwestphal.de/TestgetriebeneEntwicklung.html
That green/red bar thing keeps the text clean from
"now the bar is green".
That idea would be worth stealing, or asking Frank for
permission.
cheers
Dierk
self.log = self.log + "testmethod"
This seems to be a little smelly to me, because we
instrument the production code with functionality for
testing purposes.
But with pyhton, I cannot show an alternative.
It is less smelly if the logging is needed anyway and
I "reuse" it for testing, like Log4J and StringAppender
where I can set the loglevel in the testcase, leaving
the production runtime untouched.
cheers
Mittie
> There are two schools on writing tests to force you to
> replace "10" with
> "amount * rate".
Lately we used "negative tests" to drive the code from
constants to calculation.
starting with an assert and a constant implementation
we write a second assert that shows how the code is
expected to fail.
It gives very much the impression of "code massage".
I like that picture. It's like bending in two directions.
In german we came to the idiom "den code durchkneten"
(to kneat).
cheers
Mittie
> I've uploaded a version that implements testEquals before
> testMultiplication. See how you like it.
Well, now the problem is, that we're not starting with
our goal, but with an intermediate step, that is not
motivated directly.
This is still because we try to use the Value Object
Pattern. This is an up front design decision.
It is this decision that keeps making problems.
I tried to go around it, but I confess that I found
no viable solution whithout giving up that pattern
decision at least for the start.
I tried both, the book example and an alternative
in my IDE, really coding it.
The alternative was:
public void testTwoTimesFiveBucks()
{
int bucks = 5
assertEquals(10, bucks*2);
}
----
An easy start for the reader.
Gives a greeen bar.
Teaches about JUnit use.
Holds as long as there is no other currency.
----
When GBP comes in, we need to distinguish it
from the USD so we refactor (while keeping
the bar green)
----
public void testTwoTimesFiveBucks()
{
assertEquals(10, new Dollar(5).getAmount() * 2);
}
---
new functionality:
---
public void testTwoTimesFiveBucks()
{
Money fiveBucks = new Dollar(5);
Money fivePounds = new Pound(5);
fivePounds.isWorth(new Dollar(10));
assertEquals(new Pound(15).getAmount(),
fivePounds.add(fiveBucks).getAmount());
}
----
This was totally driven by intention. Really.
I'm not cheating.
----
Everything now falls in place like magic.
Duplication in Dollar and Pound is resolved
by pull up method.
----
I was concerned aboud class bloat on too many
currencies - but - it never happend.
I expected the need for the refactoring:
"replace inheritance by type code" :-))
To be honest, I became suspicious about the
rating of value object. It was not backed at
all from my concrete TDD experiment.
When programming, the alternative was much easier
to do for me than the original example. I did less
of my stupid programming errors due to static type
checking by the compiler and the code completion that
saves me from all the annoooyyyiiinng typos.
I had less tests failing unexpectedly. (no count here,
only impression)
It is my considered opinion that the original example
is a violation of TDD and that the alternative is
superior in all aspects but one: it is much harder
to show how to go off road.
Please don't be offended. I would not be so bold,
if it wasn't important to me.
cheers
Mittie
Around Monday, February 11, 2002, 8:50:29 PM, Kent Beck wrote:
> Re: "really working code". I was programming, and running JUnit, as I was
> writing. It gets a bit mixed up now, because I am revising code mid-stream.
> I certainly intend to put the last version of the code on CVS, and I'm not
> opposed to having a repository somewhere that has real versions at each
> point where the tests all run (or some such).
On a related note, Chet and I wrote a thing in Ruby that saves all the
files that have changed every time we run the tests. We will probably
enhance it to only do it if the tests are green. We figure that it'll
take a lot of saving before we even care about compression ... and we
can find any version we might ever want. It's a step on the road to
... well ... to somewhere.
Ron Jeffries
www.XProgramming.com
The fact that we know more today, and are more capable today,
is good news about today, not bad news about yesterday.
Re: "really working code". I was programming, and running JUnit, as I was
writing. It gets a bit mixed up now, because I am revising code mid-stream.
I certainly intend to put the last version of the code on CVS, and I'm not
opposed to having a repository somewhere that has real versions at each
point where the tests all run (or some such).
I like the suggestions for sidebar stuff. My only hesitation is that I am
still in Word, which doesn't handle marginalia well. I have to bite the
proverbial bullet soon and move to Frame. Sigh...
Kent
I believe the interplay of two characters is essential to explaining
how dynamic TDD is, but as Steve points out, there's no visual clue
that it's going on.
Using a different typeface or style would be wearing. Colour would
work, but I fear it would be wasted on this binary dimension.
Plays have a character name in bold before each line: too invasive.
How about putting something in the left margin.
Or simply using indentation, as I'm probably just
playing devil's advocate, e.g. I might argue you
didn't need divide as it's multiplying by the reciprocal.
Who said that?
I/We like the to-do list in the side bar and would reserve this area
for things to do with record the passage of time in the process. I
would suggest you had a little icon for versioning code, and
releasing. I'd have versioned after that first green bar.
Then came something I almost missed, because the screen shots of the
first two runs of the tests took up so much page real estate:
You improve the Money.equals test (testEquals) with a test for null,
then implement, but I didn't see your null test failing. Now we don't
want to keep rubbing the readers face in that huge screen shot, and
pointing out failure is progress, but without an indication of the
process of TDD, I think we loose something.
I'd have a red bar across the page. Right across margin to margin,
it's a sequence point in the process. Then you have the
implementation. Then have a green bar right across.
After the green bar in the right margin, have a little version icon.
If you use this queue consistently throughout the book, their impact
is in their "small multiples" (Edward Tufte - Envisioning
Information). They show how often we really run all the tests.
Continually reading this in the narrative would be painful and isn't
the same as when you're really doing TDD looking at the green bar
smiling with confidence. Aha, maybe you have a :-) next to green and
red bars you expected, and a :-( next to a red (or green) you didn't
expect.
I'd also try to come up with a to-do list style way of showing the
test failures. The screen shots are too cumbersome, maybe have one,
then move to a minimal form in the right margin. My failed tests
_are_ my to-do list :-)
My last crazy idea is that all your code should be in CVS somewhere
and you should build it, and put the test output in the flysheet. I
really hate books with code that doesn't work, and with a book on
testing, especially "test first", your asking for trouble. I have
ideas on how I'd do this if your interested.
Oli
Bye
I've uploaded a version that implements testEquals before
testMultiplication. See how you like it.
Feel free to send stupid typos to me directly instead of reminding the whole
group just how many mistakes I make.
Kent
There are two schools on writing tests to force you to replace "10" with
"amount * rate".
The first school I call Triangulation. It predicts that you wait until you
are converting 10 USD at 2:1 and 20 USD at 3:1 before you can have both
variables in the target statement. I do not generally subscribe to this
school, although I can do it if necessary.
The second alternative is FITYMI (Fake it til you make it). This says that
you first create a constant response, then refactor to eliminate duplication
between test case and code.
I've tried Triangulation, but the steps don't seem valuable to me, so I
quickly lose motivation. When using FITYMI I occasionally slip into
premature abstraction, but generally I'm much happier with the steps.
The third alternative is, I suppose, just typing in the correct
implementation from the get go, variables and loops and all. This is kind of
third gear, and is probably what I really do most of the time.
Kent
Proactive QA would write application-level tests in preparation for
presenting stories to the development group for detailed estimation and
implementation. They might also use statistical techniques to predict where
defects were most likely, and alert development to the possibility.
Kent
Value Object and Immutable are the same pattern, near as I can tell. The
point is that you are using objects in a degenerate way, like values, not
like things whose state changes over time.
You are right, though, that we can record the design decision to make Money
immutable before we go on to multiplication--by testing for and then
implementing equals. There is a pedagogical question of whether we want to
do this, but I'm inclined to try it and back out if it doesn't work.
I'm amazed as I write this book just how much farther I can push TDD than I
ever thought possible.
Kent
Around Monday, February 11, 2002, 4:51:30 AM, Dierk.Koenig@... wrote:
> edwardhieatt wrote:
>> Shouldn't we demonstrate that it's not right by writing
>> another test
>> which expects something other than 10 as output? Then
>> that test
>> would fail, giving us motivation to change the code.
>> We'd
>> change "10" to "amount*multiplier", and bingo, both tests
>> would run
>> and we'd be done.
> I suppport this point.
> We could start by passing back a second constant, selected
> by "if".
> The driving force for the replacement would be rule No. 2
> "remove duplication".
While what Dierk and Edward are saying is one good way to go, I think
it's not the only way to go. The programmer already knows how he got
the 10. (The author might comment on that earlier, to make it clear,
but the 10 was always 5*2 in the programmer's mind.) Therefore it's ok
to use that information on any green bar to /refactor/ the code.
There /is/ a tiny taste of YAGNI in what goes on, but it's ok to find
the balance on the size of tests. And I think it's /very/ important to
show that refactoring as we go is good.
In the case (I'm not sure if we're talking about it here) where there
are two lines of code, the one with a constant and the one with 5 * 2,
the replacement of the 10 with 10 * 1 is, as I commented earlier,
extremely masterful (in the sense of master, journeyman, apprentice),
and deserves to be brought out. JMO, of course.
Ron Jeffries
www.XProgramming.com
Sorry about your cow ... I didn't know she was sacred.
edwardhieatt wrote:
> Shouldn't we demonstrate that it's not right by writing
> another test
> which expects something other than 10 as output? Then
> that test
> would fail, giving us motivation to change the code.
> We'd
> change "10" to "amount*multiplier", and bingo, both tests
> would run
> and we'd be done.
I suppport this point.
We could start by passing back a second constant, selected
by "if".
The driving force for the replacement would be rule No. 2
"remove duplication".
Two concerns:
1) The second test breaks a nice property we had so far:
tests that we assume to be equal are represented by
exactly one example. (in german: "Testen von
Äquivalenzklassen"; could not find a better translation)
Breaking this property we introduce duplication in the test suite.
2) To me it is a general problem to work like a computer,
because I am not a computer, but a programmer (pea sized
brain, sore fingers, and so on...) Following the "rules"
of TDD to make something work - like a computer would follow
a program - is just not for me. (In fact I think that this
is a major drawback of most "processes" like RUP etc.
They _would_ work if programmers _would_ follow the "program"
like "processors".) We need to come up with a solution that
takes advantage of our accidential deviation ("jitter").
Wo could tackle problem 1) by deleting the second test
after we did the replacement. (scary, scary,...)
We could tackle problem 2) by putting this strategy in
the list of things you should master as an "etude" but
only execute in the "smalles possible step" scenario.
We would profit from the deviation "bigger step" as
long as it works. When we burn our fingers with it,
we can back out and follow the safe route.
I support the point that we must show the safe route.
cheers
Dierk
edwardhieatt wrote:
> Shouldn't we demonstrate that it's not right by writing
> another test
> which expects something other than 10 as output? Then
> that test
> would fail, giving us motivation to change the code.
> We'd
> change "10" to "amount*multiplier", and bingo, both tests
> would run
> and we'd be done.
I suppport this point.
We could start by passing back a second constant, selected
by "if".
The driving force for the replacement would be rule No. 2
"remove duplication".
Two concerns:
1) The second test breaks a nice property we had so far:
tests that we assume to be equal are represented by
exactly one example. (in german: "Testen von
Äquivalenzklassen"; could not find a better translation)
Breaking this property we introduce duplication in the test suite.
2) To me it is a general problem to work like a computer,
because I am not a computer, but a programmer (pea sized
brain, sore fingers, and so on...) Following the "rules"
of TDD to make something work - like a computer would follow
a program - is just not for me. (In fact I think that this
is a major drawback of most "processes" like RUP etc.
They _would_ work if programmers _would_ follow the "program"
like "processors".) We need to come up with a solution that
takes advantage of our accidential deviation ("jitter").
Wo could tackle problem 1) by deleting the second test
after we did the replacement. (scary, scary,...)
We could tackle problem 2) by putting this strategy in
the list of things you should master as an "etude" but
only execute in the "smalles possible step" scenario.
We would profit from the deviation "bigger step" as
long as it works. When we burn our fingers with it,
we can back out and follow the safe route.
I support the point that we must show the safe route.
cheers
Dierk
Hi all -
I like the book very much.
Here's a worry I have: the top of page 15 where we have a test that
runs because we've hardcoded the "10". Then we replace "10"
with "5*2", and then replace that with "amount*multiplier". Good, we
are saying, the test still runs, and now we know *in our minds* that
the code works in cases other than just when 10 is the right answer.
My problem is: where's the motivation for replacing "10" with
anything? We say that you change code when a test is failing or we
want to refactor. But:
- the test runs
- we're not doing a refactoring because we're clearly changing the
behavior of the code
So why are we changing it? We're changing it because we know, in the
back of our minds, in a non-test-driven way, that it's not right.
Shouldn't we demonstrate that it's not right by writing another test
which expects something other than 10 as output? Then that test
would fail, giving us motivation to change the code. We'd
change "10" to "amount*multiplier", and bingo, both tests would run
and we'd be done.
The issue comes from the fact that we start out by hardcoding the
right answer to begin with (which we need to do to get the test to
run, given the stage the code is at). Because of that, I think we
need to write a second test as we progress in the generalization away
from just "10".
Am I missing something or being too pedantic? I guess the reason I
think this is important is that the point is to *drive* code with
tests, which I'm not sure we're strictly doing here.
- Edward
That NullPointerException thing.
Well, Java is not really elegant here, but to me it is
good software craftsmanship not to throw misleading
exceptions.
I would suggest:
testMissingRates()
{
try
{
exchange.convert(new Money(10,"USD"), "XXX");
fail("should have thrown IllegalArgumentException");
}
catch(IllegalArgumentException expected)
{}
}
That one is a RuntimeException as well.
I'm sorry for my typos like "examl" and GBR instead of GBP.
cheers
Dierk
This time for sure
p.27
assertEquals(new Money(10/5,"CHF"),francs);
I always try to avoid calculations in the expected values
like the 10/5 here. It does an implicit "floor" when
converting to int, which works, even with 10/3 because the
production code does the very same.
But that is an aspekt that is not communicated at all
and is fairly easy to miss for the casual reader.
cheers
Dierk
to-card on the right: great idea! excellent visualization!
Add and Subtract acceptance test:
Why does "Add" return Dollar, while "Subtract" returns Pound?
"Add USD 5 to GBR 5 .."
maybe
"Add GBR 5 to USD 5 .."
would make it clearer what currency is expected as return:
the one that was provided second.
cheers
Dierk
That is great!
Showing how the process begins to smell
while the code entropy rises.
This is really useful, for the reader can
detect this situation at work.
The closing paragraphs follow the pattern
of starting with a startling sentence and
then shading some different light on it.
As a reader I had to get used to it before
enjoying.
minor typos:
".. you are [a] wrong to .."
".. steps than even than the failing steps .."
cheers
Dierk
Todo
Learning when not to test.
when asked what I need to test, my usual answer is: "my logic"
stressing both words
"my" means
- not to test third party packages
- not to test my db's sql engine by sending statements
- not to test the db population
- not to test whether my browser displays valid html correctly
"logic" means
- any code that has a conditional like if(), for(), etc.
- any control flow like through exceptions and polymorphism
- sequence of statements usually has no logic, but can have
in case of template method (polymorphism) and where I
must ensure the correct order of execution
I found the two words definition to be a nice analagon to
the two sentence definition of TDD.
cheers
Dierk
Hiall,
Frank pointed me to this group and I'm sorry to start with a rather
compelling statement: IMHO the example violates the rules of TDD as I
understood them.
The example starts with two design decisions:
- use the "value object" pattern (Ward Cunningham)
- use the "immutable" Pattern (Doug Lea)
(btw. as I understood Ward, value object only means to bind
the "amount" to the "currency" for it makes no sense without.
They just happen to be "immutable" sometimes, but this is not a defining
property.)
So, we have two design decisions in a row.
And that before the first test or line of code was written.
Actually, these decision are the main reason the upcoming "equals"
problem, that brings us two methods away from the greenbar, breaking
two tests. When I reached that point, I really expected the sentence:
Hey, we're stuck. Test tells us that our first attempt was wrong.
OK. fine. learned something. let's start over.
(Problem was: too much design up front.)
How about this?
public void testMultiply1()
{
int tenBucks = Calculator.multiplyDollars(5,2);
assertEquals(10, tenBucks);
}
Oh, how awkward! Communicates badly! Smell: magic number.
OK, you never have to say: "I'm sorry".
Next try:
public void testMultiply2()
{
int tenBucks = new Dollar(5).times(2);
assertEquals(10, tenBucks);
}
Implement. Greenbar. Short feedback cycle!
NOW we may go into direction "Immutable".
public void testMultiply3()
{
Dollar tenBucks = new Dollar(5).times(2);
assertEquals(new Dollar(10), tenBucks);
}
Does it make things easier? Does it solve any
Problem at this point? Does it produce Problems?
Yes: the equals Problem. With lots of work that
provides a functionality (equals that is) that
nobody demanded. There is no need to do that.
Backout to testMultiply2.
Starting from here, the next step may be
a PoundSterling class. Which we can make work.
Greenbar. It will have Duplication with Dollar.
Refactor.
This is how I interpreted TDD so far.
Please correct me, if I'm off track with this.
cheers
Dierk
> Don't forget, strings should be compared with .equals(), not ==
> for example "GBP".equals(currency)
Whether == works instead of "equals" depends on the JDK's
implementation of "String". Most impls have a maximum number
of chars for this, so "CHF"=="CHF" may be true, but
"Indian Rupees"=="Indian Rupees" is false on most JDK's.
Beside never using == for String compare, we could
also test the jdk's implementation in a testcase :-)
or test Money for never having more than 3 chars
as the ISO currency code implies anyway.
cheers
Dierk
first impression: it's sharpened up a lot recently.
- Preface
"(You'll have to work on grumpiness...)" - should be separate bullet
point
"4WD" -> "4 wheel drive" (for those with english as a second language)
"hoohaw" -> hooha (from the Yiddish)
I suspect it was my comment that prompted the note about the style. If
you're going to do it, don't apologize, it's the house style anyway. If
you want to give the feel of two people talking ("Schizophrenic? Us?"),
how about putting some of the asides in another font to make the point?
You could play it like Jimminy Cricket (cf. Pinocchio).
- Example: Money
Money.equals(), why the parentheses around currency.equals() ?
- This time for sure
By leaving out the second parameter (for now), you're concentrating just
on how the pieces fit together -- how the code "flows". Once that's
stabilised, you can add more detail.
Exchange.addRate() - if it doesn't do anything, leave it out. You can
add it when you're ready to use it. In the meantime, you could have an
instance variable that you initialise to 2, or how about adding it to
the constructor if you want to make it visible?
exchange = new Exchange(2);
Don't forget, strings should be compared with .equals(), not ==
for example "GBP".equals(currency)
Guess you don't like conditional expressions:
return ("GBP".equals(currency) ? 2 : 1);
The one advantage they have is that they make it clear that you're
returning a single value.
Enough for now.
S.
I'll try to save a few up this time. Might not succeed, I'm getting
tired.
=========
The rate creation on pp 24ff is /profound/. It's something that I
have never seen anyone but you do. (Others may do it every day, but
I've only seen you do it. I have seen many people goggle when you do
it.)
It needs to be brought out somehow, so that people won't miss it. Put
this on your list.
=========
it's "ach, ptooie", not "ack, ptoi", which is pronounced ack as in
back, p-twa as in "accroche toi a ton reve". I'm tired of having to
explain these things over and over.
A regional alternative is "hock", but I don't recommend it. "Hawk" is
no longer considered appropriate.
========
I'm bogging down on the Wallet. Probably I'm tired. I'll pick up
there next time.
It's an amusing book. It has learning in it. I like it so far. Up
till now it wasn't too hard to read. It might not be too hard now, I
might be insufficiently intelligent now.
G'nite ...
Just reacting while things are fresh. Get over it. This is how I
think.
You're telling people (repeatedly) that if this seems crazy, don't do
it.
Maybe I'm too extreme, but remember etudes.
If this seems crazy, do it until you're so bloody good at it
that you can't remember why it seems crazy.
Then decide if it's crazy.
It's called Extreme Programming, not Rational Programming. That one's
taken.