YouTube Transcript:
Test Driven Development (TDD) Is A Broken Practice • Dave Farley • GOTO 2025
Skip watching entire videos - get the full transcript, search for keywords, and copy with one click.
Share:
Video Transcript
I recently came across a critique of
test room development that I've heard
before and often but don't really
understand. I get stuff wrong all of the
time which is exactly why I prefer test
Hi, I'm Dave Farley of Continuous
Delivery. Welcome to my channel. If you
haven't been here before, please do hit
subscribe and if you enjoy the content
here today, hit like as well. The
critique that I came across was from Art
Zachenko. Here it is. In it, he says,
"Test room development implies writing
tests before writing the code. You write
tests to describe the intention behind
the system that you want to build, how
you expect it to behave. TDD heavily
implies that you should know how the
system behaves before even implementing
it. You should know what you're doing.
Most of the time you don't. All the
developers that I know don't know that
ahead of time. I don't know that ahead
of time. So yeah, okay. I can agree with
most of that. I don't think that I agree
with it in the way that meant though
because he goes on to say, "I don't see
test room development as a viable
approach in fast evolving systems like
the web." Well, first I confess I found
it slightly amusing that people writing
web code nearly always assume that it's
different and unique to writing any
other non-web code. It's not that I'm
picking on web developers here because
back-end developers call me out because
their code is special, too. So do
developers of embedded code, safety
critical code, package systems, banking
systems, games, and pretty much every
other type of code that people write.
We're all special cases, and so the
recommendations of how to do a better
job don't apply to us is seemingly what
they're saying. In the case of
test-driven development, there are
examples of organizations doing it
successfully for all of these different
types of systems and many, many more. So
maybe the special cases even if they are
sometimes different aren't so special
after all. Actually reading artim stuff
there is a lot that I can agree with.
Yes it is disappointed when pe people
take the wrong message. But describing
test-driven development as pedantic
constraints that no engineer can follow
is simply and obviously factually
incorrect because here I am a software
developer who can use these so-called
pedantic constraints to my advantage
even for that apparently tricky form of
uh coding writing web apps. To be fair,
I I do like have to give credit. I do
like a falsifiable statement and even
more so when I get to falsify it. I
suppose it depends on the nature of web
apps that we're talking about. If your
web app doesn't do very much and so it's
mostly about playing with the aesthetics
and usability, then yeah, TDD is not
lots of help for that part of the
problem. But my point is that I want to
test the other bits of the system. the
SIS the stuff that constructs the models
that I may later display retrieves and
stores the information interacts with
the other parts of the system. Then I
can keep those tests passing so that I
can more freely change the parts of the
code that that are focused on the pixel
painting and more freely experiment with
usability and nice look and feel.
Actually, I saw Artm's tweet because
Kent Beck responded to it on Twitter,
and I saw Kent's response first, which
naturally I'd recommend. Kent outlines
his take on the importance of small
steps with a simple example, showing how
we can make progress in unplanned
incremental ways. Even when we don't
have a grand master plan of the eventual
destination, I wanted to pick up on
essentially the same point of view as
Kent, but from a slightly different
angle. This represents a common
antiattern that seems to me to be at the
heart of RTM's post. That is imagining
the code that you will write when you
write the test. This is a very common
mistake for TDD beginners. Um they're so
used to diving directly into
implementation that they can't prevent
themselves from doing it even when
they're being asked to start somewhere
else. In this case, by writing a test.
So what they do is because they are
being nagged by someone like me, a
trainer to start with a test, they
imagine the code that they will write
and then they write the test to test
that imagined code. This has the same
failure mode as writing your test after
the code. Your test will be now be
coupled to your implementation. In this
case, an imaginary
implementation. What Kent is talking
about in his post is very different to
this. this and does not rely on any
perfect foresight at all. TDD is a
process of dividing the design of our
software into a series of small actually
tiny steps that incrementally evolve our
software towards more effective higher
quality solutions. This is the real
trick, the real skill of test
development to learn a more incremental
approach to design. So this is a million
miles away from clever developers
intuiting perfect solutions before they
start. I think that not only that this
is a misread of the intent of
test-driven development, but actually
misses what I think of is probably the
most important value that it brings.
Test-driven development creates a
pressure on us to work in these small
steps. In my experience, the more you
practice test-driven development, the
better you get at reactively steering
the design rather than attempting to
intuitit it from the start. In the long
run, this gives us a more effective,
more scalable, higher quality approach
to design overall, giving us clear
feedback on our design choices after
each small step. If you'd like to give
test room development a try, do check
out my free tutorial or maybe even the
paid for full more extensive TDD
training course. There's links to both
of those in the description below.
Test-driven development is a more stable
approach for lots of reasons. But one of
the ideas that may seem subtle but is
deeply important in terms of the
stability and effectiveness of this
approach is that it keeps our options
more open after each small step. Which
is why contrary to assertion and the
doubts of the people that say that TDD
isn't possible unless we have perfect
foresight, TDD works better than the
alternatives when exploring new ideas.
Let's imagine three ways of designing a
system. big upfront design which I'm
pretty sure RTM's not recommending an
exploratory approach as he describes it
and TDD or what I think of as
exploratory with tests in big upfront
design you think really hard and
depending on the scale of the problem
maybe you try to capture your design in
some intermediate form so that you can
remember all of the detail as you go
through the process of thinking really
hard and maybe you use these models then
to explore and evaluate your design
choices in the old days, I used to do an
awful lot of this kind of thing. I was
actually pretty good at it and built
lots of systems this way, but it's
pretty limiting overall. People working
this way weren't stupid or naive,
though. They tried to find ways to break
the problem down into smaller steps. And
we ended up with modeling languages like
UML and complex development process
frameworks like rational unified process
that were meant to help. But I think
that the problems with this approach are
pretty fundamental and broadly fall into
two categories. Leaky abstractions and
lack of real feedback. The abstraction
problem is evident. Our aim is to
capture a design. In reality, the design
of our code is written in code. So,
however else we represent our code,
formal or not, if we're doing that
without the actual code, there's going
to be an abstraction layer on top of it.
All abstractions are leaky and
diagramming techniques like UML are
leaky in several problematic
ways. Some ideas are much easier to
express in code and some in diagrams. So
there can be a huge mismatch in the
levels of abstraction between the
diagram and the code either way round.
So something that looks complex in a
diagram may actually be trivial in code.
Try writing a for loop in UML. and
something that is complex in the code
may look easy in a diagram. Both of
these are bad if we're trying to make
choices about our designs based on
pictures and in the absence of real
code. Don't get me wrong, there's
nothing inherently evil in diagrams. UML
is a fine tool for conveying some ideas
clearly, but I think it's most effective
when used at a very different level of
abstraction to code. And it's a big
mistake to confuse the two.
It's too easy to express an idea in a
diagram that is just plain stupid when
it comes to the code. The abstraction
leaks. The second problem with upfront
design is how do we get feedback on the
validity of our design. There was a big
push through the 1980s and '90s to try
and formalize this with more formal
diagramming techniques, defining rules
so that the tools could validate the
diagrams. This would be our feedback
that checked our designs. People were
talking a lot in those days about model
driven design assuming that we would
raise the level of abstraction for for
development by using diagrams and then
generating the actual code from the
pictures. Problem was this never really
worked out very well. It turns out that
code as text is remarkably good and
flexible at representing ideas. In this
case, a picture really isn't worth a
thousand words. Code is often more
precise and more concise. In general,
software demands an annoyingly pedantic
level of detail and precision, and it
turns out that text is better at that
than pictures. Pictures can help us
navigate though, but do a poor job of
specifying things in sufficient detail
to be reliably
executable. Code is often more concise
and more accurate representation of what
it is that's really going on. which
means that when we come to realize a
diagrambased model of a design, we'll
often find mismatches at the level of
the code. So our design assumptions can
easily be wrong until we get to the
actually implementation. The next level
up from big upfront design like this is
RTM's suggestion of a more exploratory
approach. He describes it as prototype,
iterate, test. Obviously, I think that
testing at the end like this is a big
mistake. it misses out on the large part
of the value of test-driven development
which is the impact that test-driven
development has on the design of our
code. I believe that TDD forces us to be
the first consumer of our code and so
gives us the first and strongest
feedback on the quality of our design
choices as a result. But as well as
missing out on that so valuable feedback
on our designs, leaving the testing
until you're otherwise finished also
misses out on the defensive value that
the tests give you in test room
development and and so making you more
able to iterate. How do you know that
your iterations are still working and
leading you in a sensible direction if
you don't have those tests? Practically,
I don't see a lot of difference between
RTM's recommended approach and the more
classical big upfront design because big
upfront design never knows how bad your
choices are until it's too late.
Prototypes are fine. They don't replace
test room development though. Its job is
something else entirely. I think that
RTM's mistake here is another common
though understandable one. Seeing test
room development as being primarily
focused on testing. After all, test is
right there in the name. And I agree
with him very strongly on the value of
tests and automated tests in particular.
But I still think that it is the
secondary value of test room development
compared to the impact of TDVD on the
development process overall and
specifically in terms of driving better
design choices and encouraging us to
work in smaller steps. So how does this
compare to TDD? Well, Kent Beck's point
is very important one. TDD encourages a
more incremental, more evolutionary
approach to design, development, and
testing. We proceed in small steps and
usually try to start with what seem like
the easiest steps. In my test room
development training courses, I often
use a coding exercise to add fractions
to teach this incremental approach. My
first test is usually to begin with a
test that adds the simplest possible
kind of fraction that I can imagine, a
whole number, something over one.
I don't need to have decided anything at
all about my solution at this point.
It's obvious that this is a valid
question that my code must one day be
able to answer. And I'm guessing that
this is probably the simplest kind of
addition for fractions that I will ever
need. So the implementation will be
simple. But even if my guess is wrong at
this point, there's no real cost and my
code and test will be really simple. So
I can change them easily playing with
the design freely. And once I've made my
test pass, I can still change the
implementation detail as much as I like
without changing the test. All in the
secure knowledge that I still need to
add integer fractions and then I haven't
broken anything yet. So I'm picking a
behavior that I'd like my code to
exhibit, but that won't be too
challenging and won't demand that I
write anything complicated to make it
pass. The subtlety that I think non-TDD
people often miss is that writing the
test is about designing the external
interface to your code. The refactoring
step is where we design the
implementation detail, not the red
testing step. This means that we can
often play with our design before we
have an an implementation. Even I can
and usually do play with the interface
of my code at this stage while it's what
is probably the simplest state that it
will ever be in a test calling a public
API with as yet no working
implementation beyond that it compiles
allowing me to quickly and easily play
with the public interface to my code
from the perspective of a user of it. I
can use this to play with my design for
essentially zero cost of change. This is
much easier than building some prototype
of the whole thing. Even in this very
first test, I can be making decisions
before I've decided how any of the
implementation needs to work. Before
I've even begun to think about the
details of the problem that I'm solving,
I'm certainly going to need a fraction.
And so I can start writing that. I can
answer questions like, how will my I
represent fractions in my code? How will
I create them? How will I compare them?
and so on. I've started designing with
the only overhead being two or three
lines of test code and enough
implementation free production code to
represent the public interface to the
code that I plan to write
later. I find this approach to
exploration so much more effective than
simply thinking hard and trying to solve
the problems in my head. Once I have one
test in place, I can guess at the next
small incremental step and write that.
If I don't like how my design looks as
it evolves, as I try to cope with
incrementally more complicated versions
of the problem, then I can go back a
step or two now better informed and
revisit my assumptions from earlier. All
still with tiny minute fragments of
code. No genius level foresight required
anywhere here. Mainly a slightly more
organized, slightly more disciplined,
slightly more incremental approach to
design. Ultimately, what this gives us
is the freedom to design much bigger and
more complex things because we're always
building on something that we know works
because we tried it. Relying on our
tests to supplement our memories and our
understanding of the code as the code
gets bigger and more complex than we can
easily hold in our heads. This is a much
more scalable approach. Thank you and bye-bye.
bye-bye. [Music]
Click on any text or timestamp to jump to that moment in the video
Share:
Most transcripts ready in under 5 seconds
One-Click Copy125+ LanguagesSearch ContentJump to Timestamps
Paste YouTube URL
Enter any YouTube video link to get the full transcript
Transcript Extraction Form
Most transcripts ready in under 5 seconds
Get Our Chrome Extension
Get transcripts instantly without leaving YouTube. Install our Chrome extension for one-click access to any video's transcript directly on the watch page.
Works with YouTube, Coursera, Udemy and more educational platforms
Get Instant Transcripts: Just Edit the Domain in Your Address Bar!
YouTube
←
→
↻
https://www.youtube.com/watch?v=UF8uR6Z6KLc
YoutubeToText
←
→
↻
https://youtubetotext.net/watch?v=UF8uR6Z6KLc