🚀 Does TDD Really Lead to Good Design? (Sandro Mancuso)
Summary
TLDRThe speaker reflects on their 14-year journey with Test-Driven Development (TDD), emphasizing its impact on good software design. They discuss the TDD cycle, the importance of design decisions, and the emergence of design through TDD. The talk explores different TDD styles like Classicist and Outside-In, and how they influence design. It also touches on the use of mocks as a design tool and the concept of 'inflection point' in design to balance simplicity and future-proofing. The speaker concludes by highlighting the importance of understanding design principles to effectively use TDD.
Takeaways
- 🎯 The speaker emphasizes the importance of Test-Driven Development (TDD) in shaping good software design over the past 14 years.
- 🔄 The speaker's initial skepticism towards TDD transformed into a deep appreciation after experiencing the benefits of its structured approach to design and development.
- 📚 Influenced by the book 'Thinking Fast and Slow', the speaker began to question and reassess the foundations of his professional practices, including TDD.
- 🤔 The speaker highlights the need for developers to understand where design decisions come from when test-driving code and the impact of different TDD styles on design.
- 🔧 TDD is described as a workflow rather than a design tool itself, with design emerging through the process of writing tests and making them pass.
- 🔑 The speaker identifies 'Just-in-Time Design' as the initial design decisions made before TDD, which are critical for setting the direction of the code.
- 🌟 The Classicist and Outside-In approaches to TDD are explained, showing how they both follow the TDD cycle but differ in where and how design decisions are made.
- 🛠 The use of mocks in TDD is presented as a design tool to help define interfaces and interactions between components, rather than just a testing tool.
- 🧐 The importance of understanding the domain and business requirements is underscored for making informed design decisions before writing tests.
- 🔍 The concept of 'Inflection Point' in design is introduced, which is about finding the balance between simplicity and future-proofing the software without over-engineering.
- 🔄 The speaker concludes that TDD, when combined with a good understanding of design principles, can greatly enhance the development process and result in higher quality software.
Q & A
What is the main topic of the speaker's talk?
-The main topic of the talk is the speaker's journey and insights into Test-Driven Development (TDD), its impact on software design, and the different styles of TDD.
How long has the speaker been involved in software design?
-The speaker has been involved in software design for 22 years.
What inspired the speaker to initially try TDD?
-The speaker was inspired to try TDD after a conversation with someone who suggested that TDD could complement the speaker's interest in software design.
What was the speaker's initial reaction to TDD?
-The speaker initially thought TDD was weird and unnecessary, believing it to be too slow and cumbersome for their design process.
What book impacted the speaker's perspective on TDD and design?
-The book 'Thinking, Fast and Slow' by Daniel Kahneman had a profound impact on the speaker's perspective on TDD and design.
What does the speaker mean by 'emergent design'?
-'Emergent design' refers to the design that evolves from the code as the system is being developed, rather than being pre-planned in detail.
What are the two main styles of TDD mentioned in the talk?
-The two main styles of TDD mentioned are 'Classicist' and 'Outside-In'.
What is the difference between 'Classicist' and 'Outside-In' TDD approaches?
-The 'Classicist' approach focuses on writing a failing test and making it pass with the simplest possible code, then refactoring, while the 'Outside-In' approach starts from the outer layers of the system and works inwards, defining interfaces and interactions using mocks.
How does the speaker view the role of design in TDD?
-The speaker views design in TDD as a combination of 'just-in-time design' decisions made upfront and 'emergent design' that evolves through the TDD cycle.
What is the significance of understanding 'composition' vs. 'aggregation' in TDD?
-Understanding 'composition' vs. 'aggregation' is crucial in TDD for determining the unit under test and making informed design decisions about class responsibilities and interactions.
What is the 'inflection point' in software design according to the speaker?
-The 'inflection point' in software design is the optimal balance between starting with a simple solution and preparing for future requirements without over-engineering or under-engineering.
Outlines
🎨 Embracing TDD and Software Design Evolution
The speaker reflects on their journey with Test-Driven Development (TDD) and software design, starting from their initial skepticism about TDD to full adoption. They discuss the impact of TDD on design, the importance of understanding different TDD styles, and how TDD can lead to good design. The speaker emphasizes the need to question assumptions and to pay attention to where design decisions come from during test-driving code.
🛠 Exploring TDD Styles and Their Impact on Design
This paragraph delves into the different styles of TDD, such as Classicist and Outside-In, and how they affect the design process. The speaker explains the Classicist approach, which involves writing a failing test, making it pass with the simplest solution, and then refactoring. They also discuss the importance of understanding the type of feature being built and how it influences the choice of TDD style, ultimately affecting the design outcome.
🌱 Just-In-Time Design and Emergent Design in TDD
The speaker introduces the concepts of Just-In-Time design, where design decisions are made as TDD progresses, and Emergent design, which arises from the iterative process of writing and refactoring tests. They discuss the evolution of design from simple tests to more complex systems, emphasizing the importance of making upfront design decisions before starting TDD to avoid refactoring challenges later on.
🔍 Analyzing Unit Size and Design Decisions in TDD
The paragraph focuses on the importance of determining the appropriate size of a unit under test in TDD. It discusses the challenges of growing units that encompass multiple classes and how this can complicate testing and refactoring. The speaker highlights the need to understand the difference between composition and aggregation when making design decisions, which can guide the choice of unit size in TDD.
🛤️ Navigating Design Decisions and TDD Workflows
The speaker discusses the workflow of TDD as a design tool, emphasizing the importance of making design decisions before writing tests. They explore the use of mocks in TDD as a way to define interfaces and relationships between classes, and how this can lead to better design. The paragraph also touches on the challenges of maintaining tests as the system evolves and the need for clear design decisions to avoid widespread test failures.
🏗️ Applying Composition and Aggregation in TDD Design
This paragraph examines the concepts of composition and aggregation in the context of TDD, explaining how these relationships inform design decisions. The speaker provides examples of how to determine whether classes should be tested together or mocked, and how understanding these relationships can lead to more cohesive and maintainable code.
🛒 Checkout Scenario: A Deep Dive into TDD Design
The speaker presents a detailed example of a checkout scenario to illustrate TDD design decisions. They discuss the complexity of payment processing, order management, delivery, and notifications, and how to apply TDD to each aspect. The paragraph emphasizes the importance of understanding business requirements and how they influence the design and testing process.
🔗 Understanding Dependencies and Design in TDD
The paragraph explores the dependencies between classes in TDD and how they affect design. The speaker discusses the use of mocks to define interfaces and the importance of understanding the business domain to make informed design decisions. They also highlight the need to balance the level of abstraction in the system to avoid overly complex or overly simplistic designs.
📈 Finding the Inflection Point in TDD Design
The speaker introduces the concept of the 'inflection point' in TDD, which is the optimal balance between simplicity and future-proofing in design. They discuss the importance of understanding the reasons behind design decisions and how to optimize for changeability or other factors without over-investing in the design upfront.
🌟 The Power of TDD and Design Mastery
In the concluding paragraph, the speaker emphasizes the transformative power of TDD and the importance of mastering design skills to make TDD more effective. They encourage understanding different TDD styles and having a solid foundation in design to achieve synergy between TDD and design, ultimately leading to better software development practices.
Mindmap
Keywords
💡Test-Driven Development (TDD)
💡Agile
💡Extreme Programming
💡Design Patterns
💡Refactoring
💡Emergent Design
💡Classicist TDD
💡Outside-In TDD
💡Mocking
💡Composition vs. Aggregation
💡Design Decisions
Highlights
Introduction to the speaker's journey with TDD and software design over 22 years.
The speaker's initial skepticism towards TDD and how it was overcome through a month-long trial.
The realization that TDD leads to good design, a concept the speaker has repeatedly emphasized over the years.
The influence of 'Thinking Fast and Slow' on the speaker's approach to questioning professional practices, including TDD.
The importance of understanding where design decisions come from during test-driven code.
The distinction between different styles of TDD and their impact on software design.
The classicist TDD approach and its focus on simplicity and emergent design.
The outside-in TDD approach and its emphasis on behavior and design from the system's boundary inward.
The role of composition and aggregation in determining the unit of test in TDD.
The use of mocks as a design tool to define interfaces and interactions in TDD.
The concept of 'just-in-time design' as opposed to emergent design in TDD.
The importance of making design decisions before writing tests to avoid wasteful refactoring.
The challenges of maintaining tests as the system evolves, especially with larger units of test.
The idea of using TDD to explore and understand complex business flows and requirements.
The balance between upfront design and letting design emerge through TDD.
The 'inflection point' in design, where the balance between simplicity and future-proofing is considered.
The conclusion that TDD is a phenomenal tool when combined with a solid foundation in software design.
Transcripts
[Music]
hello so the reason for this talk one of
the reasons that I'm doing on the
whiteboard is thought I could not
prepare these lights right so it was too
complicated for me to go through
everything I wanted to say but I think I
went through phases in my developer
career since the beginning I was very
inch into software design since I
started that goes back 22 years ago
about 12 years ago almost 14 years ago
actually when I moved to the UK someone
that was when I got in touch with the
whole agile thing I heard about this
thing about extreme programming I
thought it was very cool but it was too
weird and and then someone said like you
like so much software design why don't
you try this thing called TDD and I said
because I don't need that why would I do
that it sounds very stupid to me and
then someone said you know but try it
wholeheartedly really really try it for
a month it was painful extremely painful
because I felt that everything that I
was doing was it slow so I can visualize
the design already I can visualize the
solution so why don't I just go for it
and if people really care too much about
it I write a test afterwards and then
someone said nope do it and I've done it
and I've done it for a few months close
to three months and I never looked back
so I want to make it clear in this talk
that's all though I'm talking about the
design aspects of TDD or how to evolve
an application how to evolve the design
of an application through TDD this is
not a talk against TDD I just make it
very very clear what happened was for me
I said many times if you go back to some
of the talks that I have or
things that I read I wrote you will see
me over the past twelve years saying TDD
leads to good design TDD leads to good
design and I've been repeating that over
and over and over again about two or
three years ago I read a book called
Thinking Fast and Slow Fast and Slow
this book was I slapping my face it made
me realize that I do a lot of things
without questioning that I was not
special that I was not smarter than
anyone else
but it really made me rethink about
everything I took for granted including
my professions like I do TDD why do I do
that why do I think that it leads to
good design and then I and I start
paying attention of my design decisions
while one test-driving code where do
they come from
which DD style do I use and then I also
was paying attention at other people
other developers where did the design
decisions come from when they are test
driving their code there are different
styles of TDD how do they impact the
design so and then there's a lot of
people that I respect that use different
approaches and they get stuff done but I
was interested to know where the design
comes from so why I don't think that
that puzzled me is did it is a very
simple thing right so normally the flow
would be your writer write a failing
test you make it pass you make it better
right in a nutshell this is the TDD
cycle so we quite often say that this is
a design tool I'm changing my mind a bit
I prefer to think about as a software
design a software development workflow
is a way of working design needs to
emerge somehow but this I love this read
them one of the things that I like the
most in TDD
is this rhythm working very small
increment always in safe steps right I
always know where I am but I treat that
more as a workflow than a design and
I'll try to explain why so normally in
the traditional TDD approach so there
are two just to give some context there
are that's not I haven't tried that I
tried all the pans and stuff I just
didn't try to chewy raise anything so
there are normally two is it big enough
at the back classicist so one of these
stars there are there are different led
styles this is important to understand
because one of the reasons that people
don't they everyone understands this but
one of the reasons that they don't use
that in a nice way to emerge you to
create good designs is because they they
don't understand where the design should
come from and also why there are
different types of things that we are
building we might be building an
algorithm we might be building a
business feature you might be building
integration with other systems the the
type of feature that we are building
impact or well choosing one of the CTD
styles if it's if that's these Styles
not suitable to what you are building
you have problems and I think that a lot
of people don't understand that there
are different styles of T DT so one of
them is called classicists there is the
original one created by Kent back that
was back in the late nineties if I'm not
wrong
so and the other one is called outside
in outside in so they are significantly
different in these styles they both
follow this loop in here but in the
classicist approach normally what we do
we write a failing test and you go as
fast as possible from red to green you
do the simplest thing could possibly
work just make it work
duplicate hard code do what
ever but just make it work once it works
then you go to the refactoring phases
see let's see the mess that I created if
I create the master I can identify so ok
now it's time to clean up so design in
the in the closest approach happens here
this is what I call emergent design when
you are building the system when you are
trying to make it work you don't care
about anything you just care about
making it work once it works you evolve
your design how do you do that
so TDD doesn't prescribe I even asked
Ron Jeffries a few years ago it's like
what is the guideline here because this
is the problem that I have when I got to
this stage what we are saying is like I
forgot to start the timer and well what
is the guideline here so make it better
it's a bit too generic one of the things
that people take as guidelines they will
talk about the four rules of simple
design so they will be like pass all the
tests reviews intention no duplication
and fewer elements JB is probably
somewhere in the audience he has a
slightly different version of it that
summarizes a thing that reduce that to
two steps and stuff is very interesting
Murat Mike my main concern
point was is this part of this or this
this is just a design guideline where
does it come
what about solid were about DDD were
about coupling and cohesion or about
Canadians were about all those other
things design patterns so for me those
design guidelines are not necessarily
part of TDD TDD gives you this workflow
as say hey we specify what you want what
you're you want the code to do make sure
that you make it work and then you get
better but this is vague for me in order
for for me for people to do it just to
be successful with TDD
they need to understand this so what
people say well but it's helping with
your design it certainly has an
influence because you are forced to
specify first the the boundaries and
stuff but when you think about for
example there is a business there is a
behavior that I need to add to my code
when I write this test I need to design
decide which class or function will
contain that code what is the interface
what is the parameters was the return
type which layer this class is in the
bigger picture those are all design
decisions that regardless which is style
of to disease you are using you need to
make up front oh you already decided
where the codes gonna be what we will
help you in here is the internals of
that code that you might further define
but the inception the trigger where the
code that will trigger that behavior you
are designing what I called just in time
so the the code that emerge from the
refactoring I call emergent design but
the design decisions that we make
before TDD I called just-in-time design
so but we very often we don't think that
classes or defining the class to find a
method they're not with parameters and
return type they are all design so let
me explore this a bit so there is a
class assisted eg another name that some
people give to do a church across assist
is the Chicago School some people call
Detroit school Cleveland school
somewhere in the US a school outside in
quite often is also called London school
or ma kissed which I don't like at all I
really don't like these as a name
but there is a difference why those two
things were created all those different
styles there is another one that I don't
like to put it it's called TDD as if you
meant it I think that is too crazy to
talk about so the one thing that is
important to understand is that those
different TDD styles they are not
opposing each other there's not like
either or most people there are the the
best 3d practitioners that I know they
normally mix and match the truth right
okay so basically the decision is
according to what we are doing we will
pick one style and it gets so automatic
in our heads that we don't even think
about that anymore we just oh this is
what I want to build I'm gonna test
drive this way this that model it's
gonna this is what I want to build I'm
gonna test drive in a different way so
that is the the difference but like let
me try to explain to you let me focus
first on the classicist approach let's
talk about how design emerge in these
kind of TDD normally what you do here
are your tests you write your first test
according to this test you need to put
that behavior somewhere so the class
emerged so this test is forcing you to
think about where do I want to put the
behavior that I'm test driving so this
is the first design decision where the
behavior goes the second design decision
is how do i trigger this behavior that's
when the public method emerged so what
should I pass if anything to this method
that's when the parameters in word do I
expect something back that's when there
is return type in cause assists in
general well but it's what we call a
state-based test what do I mean by that
normally you will have an input so you
instantiate you put your system in a
certain state will you invoke your
system with some data and you check the
next state so
so you are always checking state so
normally the the test here is an assert
that something is equals something else
or different from some something else so
this is what is called a state base the
thing so how does design evolve we write
a very simple test we make the simplest
thing could possibly work and then
probably in the first test there's not
much a refactor you probably hard-coded
or did something like that and then what
happens is we start adding tests and as
we add more test these things becomes
more complicated and it evolves so every
test that I adhere more behavior will
come to here at some point this public
method will be a bit complicated there
will be a lot of things going on in one
of the refactoring phases what we are
going to do we are going to extract a
method like private if you're talking
about functions you might extract
another function and then you keep on
doing tests and then you are adding you
are extracting these private methods and
they are meant to just explain what the
public method is doing so the public
method instructions become more
high-level in terms of abstraction and
the details goes to the private but at
some point as the system grows quite
often we start having a need to test
these private methods we start adding so
much behavior in here that it's becoming
a bit complicated well the class is
becoming messy and then what we end up
doing one of the refactoring stages so
you know let's extract these private
methods to another class and that's when
your design is started following the
difference the the the impact that this
has is that these started as a unit so
there is a there's we always discuss
what is the size of a unit what is the
unit under test in course this is this
is more vague so well I would say that
the other one is also but like it starts
with a class or a function and as we
extract what happens is that the unit
under test becomes bigger and that's
fine so
in systems in the closest approach very
rarely we use mocks right so we just
test the whole thing as a unit and we
keep evolving like that one of the
beauties of this style of TDD is that
there's no upfront design so we just use
emergent design so there's there's a
bare minimum a fart from upfront design
that is the definition of this class
I'll talk about class and methods
because it's easier and in the
definition of the method but everything
else emerged from the code you have and
this is a good thing because this
constraint over engineering you only
design just enough for what we need and
the design emerge from your code so it's
much easier to do this style because
this is style doesn't know about
internals it knows inputs and outputs so
writing tests like that is much easier
evolving these is a little bit easier
the what how when do I use this style
when I want to explore so when I want to
explore example I have a small algorithm
I know that given this input I expect
this output I don't know what ISM in
reaching data whatever it is calculates
a discount or whatever that is right so
there will be some data coming in some
processing enrichment whatever that is
and there will be some data coming out
different inputs different outputs when
I when I have this kind of situation I
like to use this style and also when for
example sometimes you have there is an
input and there is an output but it's a
very complex logic or flow to come up
with these transformation from input to
output but there's no domain concepts
there's no nouns and verbs I cannot
really have uncurse in terms of domain
so when I in things like that you cannot
visualize a solution so this is style
working very small increments and let
that code emerge is when I use this or I
prefer this style
so things get very complicated if you
try to apply this style to business
features or to see some that have a more
complex domain let's assume that this is
feature one right so we started like
that and then you have feature two and
let's assume that this is your domain
yeah so feature two comes along and then
you're gonna write some tests and then
for this feature here you're gonna start
from your module D and you start adding
behaviors start adding turn starting
test so these gets a bit complicated a
few private methods are created at some
point you decide to move one of them to
Class E and then you say if those
classes are as soon as in your domain a
class is extracted now this class here
that the lifecycle of this class is
detach it from where it came from as you
evolve your domain those things now our
concepts our domain concepts and you
might want to reuse some of them and you
might be you might be not that you will
depend of the complexity of your domain
and and how features interrelate but you
might be in a situation where now
feature one in the future too they share
let's say these product validation these
whatever whatever this is right so there
might be because these now is a domain
concept and is used in different future
so this has a strong role in your domain
let's say there is some sort of a
discount calculator for some ferony in a
insurance company calculating the policy
the premium and the discount is quite
complex there's a tons of data that you
need to analyze in order to calculate
those things and you might need to even
access different systems so you might
have a feature as your domain gets
bigger those domain concepts
end up growing and there might be a
feature that will make this class evolve
and one of these evolutions might be you
should go to a database or chu use class
F that will go to an external system
somewhere in all of a sudden because
everything is using the real class and
you're not using marks anywhere as soon
as I evolve this class feature one is
broken future is broken
the tests are broken and then the
refactoring so what started very simple
writing very simple tests as your domain
evolves those tests become more and more
difficult to maintain because your units
are growing and you might have a need a
dependence here across features in every
now and again you have tones of tests
broken that's what a lot of people
complain I don't like to redo because
every time that I make some small change
many tests are broken this is one of the
reasons so you would you have this
problem with classicist as I said it
depends of the features so if you're
going to feature one that there will be
a class in here and then if you talk to
a few classes in here and then another
class in here and then it talks to the
database and feature to is a similar
thing there is an entry point on API or
something there is some domain logic in
here some repository that goes to the
database may be the same database you
very rarely you'll have problems with
your test because the features are very
detached this is very common in a crud
application right creating updates and a
product creation updates on a client
there's very little overlapping features
so very rarely an evolution of these
will impact on this but if you have a
more complex than then it should be a
nightmare to do this way another problem
that you have when your units under test
they become bigger is that as soon as
you make a mistake or you make a change
and our test is failing is very
difficult now to figure out where so the
diagnose of problems when they urinate
is too big the unit under test is too
big you should figure out where the
problem is this is another problem that
we have with these styles so refactoring
that started very small we learned that
we fast means a good thing but quite
often as the system evolves we fasting
becomes a big thing because the touching
all those things are evolving because if
I want to start mocking those tests from
this one well before the behavior was
expected here so we start detaching that
is not just creating an interface I need
to change all these tests as well
because now some of those tests are
relying on the the code to go through
all the classes as soon as I did touch
one there is an impact in my tests then
I need to move the test somewhere else
as well I need to inject things in
constructors so refactor is not easy as
you are evolving in this method so the
setup of those tests if you make you
keep making it into bigger become also
more and more complex as you add more
classes to these writing those tests
becomes very difficult now because they
need to take into account the whole
execution so how so how do we know so
this is class assistant I think that is
great for things that I don't have a
domain thing so inputs and outputs where
I cannot see the internals or the
internals are not about domain concepts
persist for me is awesome
but how do we define the signs of our
unit I think this is a better discussion
to have so how do I know the right size
of a unit should be the unit under test
one way to think about this is let's say
that we have Class A that somehow talks
to Class B and talks to cross see how
you got both either you create this this
way or it Class B and C a merchant from
refactoring is less relevant for the
for what I'm trying to say him so what
we need to understand is like when I
have my tests would I make the unit
under test all three classes would I
make just a and B and Moxie or would I
mock B and C because those are the
options that I have how do I decide that
first voice you understand this is this
is an association right association so a
is associated to B and C but now we need
to understand what kind of Association
we have so there are two types of
associations right one is called
composition composition and the other
one is called aggregation this is a very
important thing to distinguish so in
composition B and C are part of a the
Association means a uses B&C the the
specialization here is like it uses but
our B and C part of a or they are
independent thing that are just used by
a so when it is a composition it's quite
safe to make this a unit and test
everything together not mop those things
how do you know if this is a
compositional aggregation one mental
exercise that you can do is if you
inline the behavior of B and C into a a
will become more complex for sure but
would it become less cohesive or would
still remain cohesive an example would
be if you have a discount calculator and
during the discount has lots of
different steps I might take into
account the personal details if if the
person is married is the age and family
and stuff
might take there might be a different
part of the algorithm that will be the
credit score or how often they claimed
something under the insurance so there
are different steps but if I inline the
models your discount calculator a will
still be cohesive it should just be
messy so this is a composition so a
different way of thinking is when it's
an aggregation that relation is the
opposite if I in line B and C into a a
will not be cohesive or a very sound
simpler way of saying that a will
violate single responsibility principle
if I have a checkout process and as part
of my checkout as soon as I go to the
website and say oh that's my payment
details pay as part of the payment flow
or the checkout flow I need to verify
which payment method was chosen and then
redirect to the right gateway but I also
need to create orders I also need to
notify the user I also need to trigger
the delivery those things if I inline
those things into a a will certainly
violate single responsibility principle
so then you know that your unit under
test should just be a and those things
should be marked so this is an exercise
that is very important for us to do when
when we are test writing code so this
leads me to the question back to the
original question of the talk is is this
knowledge coming from TDD what is this
knowledge coming from design knowledge
that I already had previously right so
if I was not test driving this code
would I make the same design decisions
so and if yes or similar design
decisions the NTD D is a great workflow
that I absolutely love to work but the
design decisions the trigger for
the trigger means that the flow is
triggering so I okay it's work it's
working now now's your time to design
that's what that's why I say that is a
workflow specify it make it work now
design now let's fix it do it properly
but what I do in this phase I need to
build on top of a software design
foundation before so so in order to know
what you do so how many people like
marks don't know if I raise my hand I
don't know what he's gonna say next
right so every time I give this talk
like I talk about completely random
things and and I never cover everything
I want
so I want you these mystify marks before
I talk about the other example so how
can a backhoe that let's say that I have
a feature that is this check out thing
that I talked about let's say that we
have a web app and so there is a check
out scenario and in this check out
scenario so I want to buy my item so I
press the submit button and stuff I
don't want to go through the whole user
story now what is important here is that
there is a check out which say pay as
part of my acceptance criteria I need to
do the actual payment the payment is a
complex thing I might need to identify
which payment method was chosen which
country I am if I need to choose a
different gateways if it's paper if is
credit card so there's a lot of things
it's not just co-payment right so
there's a lot of things related to
payment in here so there's also like we
need to create an order order management
is also quite complicated sometimes you
need to create a northern with a certain
state and then wait for the result of
the payment and then update it and then
so there's a whole order life cycle into
this right so there's the delivery part
and there is a notification
vacation so delivery is like depends on
where you are in the world we need to
know which delivery options are
available to you some of them you can
collect in the local store some of them
will be delivered to your house some of
the you some place you have Next Day
Delivery some place you need to wait so
delivery is also complicated things and
some items can be delivered if you
bought in the morning can be delivering
the evening others will need to wait for
two weeks so there's a lot of things
going on in here
and notifications just let the user know
what happened after they click checkout
so I want to talk about mocking as a
design tool because like one of these
debates that we have a mob I think that
the debates that we are having about
marks are the wrong ones because we are
talking about marks as a testing tool
and not as a design tool and if we
understand that mocking can be used as a
design tool a lot of these complex these
discussions or many of those discussions
they go away so let's say that we have
these kind of scenario here let me just
so we will have a browser right so
someone is going to the browser and we
say make a payment so there will be some
front end up in here or some delivery
Macanese that can be mobile can be web
can be whatever and then there will be
so I want to test drive this flow so I
know that these thing in here will
submit right so you send the payment
send payment details or they want to
process the payments right so that's the
trigger the first design decision that I
need to make here who is going to handle
that so in that just for the sake of
this example to keep things simple let's
assume that I'm gonna say I'll have a
payment API
so these will receive the HTTP request
let's say that's the web then what I
need to decide now how do I test drive
this do I put do I build the entire
system inside one single test or how do
I evolve now this flow one one thing
that I need to ask myself if this is the
class that is handling the output
requests what is what is what is the
responsibility of its of its class you
can say wow it's you process the payment
so then is to do the whole system say ok
what does it involve in in processing
the payment what are the different steps
the different things that we need to do
well business-wise that's kind of the
flow that we want but we also have a
delivery mechanism we need to handle
these JSON that is coming in we have to
get these out of the request and it
should send a JSON back so there is a
lot of delivery mechanism logic should I
mix that delivery mechanism logic with
my domain knowledge logic or not so now
we are not talking about passing anymore
we are purely talking about designing
that's what I was thinking
did it is giving me okay you need to
write the test now but you write this
test I can have the option to write as a
black box as an acceptance test I'm
gonna send some JSON in to this API and
I expect this JSON as a result and I
might expect something in the database
somewhere or a message in a queue
somewhere and really treat as a black
box and that's one option for sure and
we should do that anyway
but it's too big for testing all those
stuff or all this stuff through from
outside so I might want you have smaller
levels of testing and that's the ones
that I'm talking about now so I only
need to decide how much behavior I'm
going to keep in here one type of
behavior I need you have is to process
to 200 call these to parse I'll just put
random names right so so we just get the
idea to parse the payload right so
that's very generic because I'm dealing
with
zone HTTP request and like that
right so I also know that these cars
will need to respond something to my
client so there will be at some point
somewhere some J's and that's going to
come back right with the response and
stuff
I will potentially we need to transform
something in here so generate response
or something like that the problem is
like what I know that this class needs
to do that
how much of these should I put in this
class how much should I delegate so what
we could do also which layer does this
class leave are this kind of behavior
knowing about HTTP should they be in the
same area of my code base or layer or
whatever from these ones or they should
be separate because if I understand that
this might be some sort of a delivery
mechanism right so this is a delivery
mechanism then I probably should not
have a lot of business in here but this
design decision is coming from my hand
it is saying like you need to design now
because you need to write this test and
this test needs to know what you haven't
wear so it's a trigger for me to design
but it doesn't tell me what you design
so then I can say well what if I well
what are we doing here I'm doing the
checkout so what if I had a class in
here that could call check out check out
action right so in domain driven design
how many of you use or art is familiar
with domain driven design okay how many
of you are you familiar with clean
architecture from Uncle Bob ok a few
hands so in domain driven design
application service in clean
architecture DC's a use case I'll just
call it actions because why not to give
another name for the same thing just to
make it very confusing so they like I
need to decide what's gonna go there so
why don't I just say make a payment at
this point this design decision what it
allows me to do is understanding where
that part of my behavior is here part of
my behavior is here and this is when I
look at the composition back to the
composition aggregation although I have
only one client for now should I check
out action because you another way of
some people say well if there is only if
the payment API is the only concert use
checkout is a composition because if I
kill payment API this class dies because
doesn't have any any client this is a
fair thing to say but logically if I
understand the kind of responsibilities
of each class has and the force that
they will have to evolve they are
different and in this case although it
has a single client I would still treat
that as an aggregation and not a
composition so I would be very
comfortable to unit test this class
mocking this class what is important in
here is to understand that these
exercise we are now using mocks
because this client the class the needs
of these classes to say look I will deal
with the the JSON stuff but I want a
class that can help me hey helper make
this payment for me and give me a result
and then I will do the transformation to
the client so this need I can use a mock
to define what this thing is and what
this interface will be so for me mocking
is a design tool and then I can go back
to what I had in here
check out payment so that the check out
logic involves sub steps in the flow
payment create order deliver
notification so first off we can have
like so check out
what is the relationship between
checkout and payment the payment logic
the order logic the delivery logic and a
notification logic I now need to
understand because now I'm done so I
will say butcher unit tests this one
mock this one and I can test my entire
navigation if I do that for all my AP
eyes and stuff I can test all the
navigation just mock in the backend now
I need to continue so okay but this was
not done yet so what is the role of this
class so if I call them make a payment
first and then as I'm mocking these I'll
need to define which pair unto them that
I have in what's going to come back so
this is where the mock is a design - it
allows us to say that the behavior is
going to be here that's how I access the
behavior that's the what I need to pass
and that's what I will need to return so
we can use mock to design that interface
and then you are done with this then you
can move inwards so you are coming from
outside and moving inwards now I go to
this class in here so I will this class
now has a make a payment and this class
was designed to satisfy the client class
right so the reason that this cause
exists with these is to satisfy a client
so now I need to ask if I call this
method what should happen oh we need to
process the payment and there is a lot
of complication here we need to create
the order there's complication there is
delivery there's notification okay so
that is way to marching here
how much behavior do I want to keep in
the checkout action and how much we here
do I want to delegate that's our again
another design decision and then I need
to understand in order to make these
because I'm test driving right so I
wrote the test for this class and now
I'm writing test for this class at a
unit level so I need to decide that the
boundaries the unit so the unit under
test for this one doesn't include this
one now I need to say what is the size
of this unit so would payment have
different forces to change from pair to
the checkout
when you do some analysis they say well
there are different business people in
different areas let's say hey we would
like to support PayPal or another credit
card or in certain countries you are not
allowed to use certain payment methods
so there is a lot of business decisions
that happen behind the scenes by
different actors that impact the
evolution of this area of the system and
that is completely unrelated to the flow
the flow would be the same you do the
payment you create the order you do the
delivery do the notification but the
details of what happens in here is not
only evolution of the code is evolution
of the business as well
right there are business decisions and
agreements and contracts signed in order
to provide certain payment methods in
certain countries so when you analyze
the domain and the business it's very
safe to to make a decision that this
sees an aggregation because these will
evolve err has a chance to evolve
completely independent from the rest
order is the same order management is
not only important when you when you try
to buy something there is a department
in the back office of the organization
that is dealing with disorders customers
will call in and said where the hell is
my package I paid and I haven't received
or what it was broken or it was not what
I wanted
so there is a whole set of group of
people and systems that we need these so
is pause so it's also quite safe
she was filmed that the logic related to
order should not be part of the checkout
should be used by the checkout or should
not be under the same unit because the
force is to evolve those two I don't if
they evolve as long as the interface
remains the same I don't want this
should be broken and then the livery is
the same thing delivery is a very
complicated thing if you when you have
like these international companies
delivering different countries they have
deals with local people local companies
to do the actual delivery so this also
has a lot of business people behind and
involving these certain delivery methods
are not available in certain countries
are not the notification I decided on
you know once this is done and I get
there is response
in this case I'll probably will make the
notification cuz the notification now is
just to send an email out and stuff I
might have some classes to do that
separate from this but it's part of the
thing if I inline this one into here
this would still be reasonably cohesive
I'm just making it up let's just to give
an example
those are design decisions and you need
to make those design decisions before
you write the test because otherwise how
do you test it if you are trying to it
you can make afterwards but that means
that you're you need to start putting
all this logic inside check out in the
green so going to red to green very fast
and populating the check out module with
a lot of logic related to these two
these two these two wait for the one of
the refactoring cycles to start
extracting then and then move the tests
across and then decide where they're
gonna put the boundaries is very
wasteful to do this way I think that you
can do a very quick analysis and make
this decision up front so once we
understand that when you do these design
analysis how much times I have shaped I
could go I could stay here forever as
always just put this back so these
design decisions in here it made just
worse right so now it's very safe for us
to start for example well let's put a
payments clause in here that could be
mocked and then I say like in order to
test drive the check out action I
already understanding my domain I made a
decision that there will be some logic
in here so this is my design decision in
my test I said I'm gonna call this class
but I'm gonna delegate part of the
responsibility to this class so how
would this flow work how do I access
that behavior so then I can say well it
could be I don't know hey could we have
a method to come pay and I need to
decide what parameters it would have
what the return type so mocking now you
allow me to go to the details what do I
pass what
my return and stuff like that I can also
have an orders which I also need you
decide I create or something like that
what's going to go in what's going to go
up again I can use a mock in there to
refine the details the delivery as well
right so sheep or whatever ships your
client ships your client and then there
is a notification side the notification
I decided to keep inside check out so I
can have a process in here that would be
send notification so this could be a
private method in here could be another
class hanging off this one but it's part
of the same unit so in my test I could
mock those three but I don't need
potentially mock these unless it is
going to a email sender that I mock at
that level that boundaries but what is
either seen in here is that now I know
that I will need to finish the contract
in here what comes back right so what is
the result of these these things so
there will be some sort of a result in
here or whatever there will be I need to
define that in my marks and then I will
populate and then return but these
allows me what this is what we call
outside in TDD that's the difference
between the classicist we start from the
outside of our system and you say like
what is the first class that's going to
handle the external world or that the
request from the external world this one
what this class should do and then you
list a bunch of stuff and if you are a
if you list more than one we need to
decide how many of those things I want
to keep inside and how many of these
things I want to delegate and then you
apply composition aggregation should
decide that and then a few things we
were merged so this class is injecting
should this one this one into this one
this one into this one this one to this
one so now we have these to inject into
here three classes injects into here
these give us a hint of whoa
do I have one class talking to too many
because if we have one class talking to
ten classes it to be mark hell
we cannot mock we cannot blame marks for
the design decisions that we are making
but this is allowing us to explore if I
see that one class is talking to too
many classes of course it will be
complicated to test that but I have what
we call in couplings called fan-out is
one class that talks to too many imagine
the Russian those that normally go
through big ones and to these more ones
gracefully right so they are big level
of abstraction or high level of
abstraction to low level of abstraction
this is the same if we start adding more
mocks to this one or more collaborations
you have a big Russian doll full of it's
more Russian dolls inside and then what
you what you need to do is just say hey
maybe some of these collaborations here
some of these calls I could group them
into another class and then instead of
having one class talking to ten I could
have one class talking to three and then
each one of those three who took channel
who talked to another two or three and
then it starts distributing the the
degree of abstraction of your system
gracefully and then mocks would not be a
problem in outside in TDD
this is the execution flow right
execution flow it's completely focused
on behavior
the other one is focus on state this the
design direction is alongside the
execution flow the test directions are
aligned as well so I normally prefer
this kind of approach when unbelief
flows but but this approach would not
work for the discount calculator imagine
that one of those things in here
somewhere another class in here there
was a discount calculator and there was
a method calling calculate discount
that's your data for that cause any
specific as I how do I evolve these
I know that these data comes in these
data and this number comes out I would
flip to cross assist I would work in
very small increments write a test
right a bit of code just to make it an
enough to pass look at it and see if I
find some duplication if I find some
patterns if I find some different levels
of abstraction and I keep doing and then
I at some point something we were merged
so is the degree of confidence so if
something I cannot see if I cannot see
the solution or the abstractions when I
when I when I think about the inputs and
outputs
I go classicist and I let the design
emerge as I guess I gain more insights
but if I can clearly clearly understand
these I won't do that the other way
because it will be very wasteful I will
go straight for the abstraction that I
want so I could stay here forever so let
me just go to one of the how much time
do I have zero two hours descent there
are there is a thing that I want to go
very quickly
I call it inflection point inflection
point imagine that this is the most
straightforward solution you can have
straight forward and this is the future
proof so normally when we design
software we can go all the way what the
future will look like and then we try to
do this big design up front the opposite
of it is the under engineering is like
just let's do the simplest thing could
possibly work both are bad in my view
and I think that what is important here
is
let me just we need you when we are
designing why don't we use just strings
and ins why do we want to create a type
why don't you do it in the procedural
way why do we create a class or a method
why do you create components why we use
hexagonal architecture why do we divide
our system in different modules why do
we want to go micro services there's
those are all design decisions just at
different levels so but why do we make
them if I'm discussing what if none of
you I might say look I want to start
small and grow and you might say no I
want to start a little bit bigger so why
do we so given the same requirement why
would we design this differently at
different levels because we are trying
to optimize for something so why we are
not doing the most straightforward
solution not because we like to
complicated things it's because in our
head there is something either through
our experience or because of our
knowledge of what is coming or what
you're going to be having problem with
very soon that we start moving this is
slide to look you know I want you
optimizer a bit more I know that we
could do a little bit simpler but maybe
we could create this interface in here
instead of having these JDBC codes or
this database code in setup inside of
our page let's create a few layers but
that would be simpler well depends on
how you define simple but if we want to
push that down the the database code
from the page to our repository going
through a delivery mechanism and your
hexagonal and stuff why are you doing
that if you don't need it's because in
your head you are optimizing that for
changeability
or to separation of concerns so those
are optimizations and you need to
understand how far do you go so how do
we find these this point when we are
designing one of the things is
understand why what you are optimizing
for and then we can come from different
directions so I can go from left to
right and say look how far can I go
knowing that we need to have a Anessa
layered expert transactions per second
or we need to support a new credit
are we are building a credit card
feature now visa and our next feature
these are max so why would I do
something very specific for visa knowing
that I will do Amex is straight away
after that why would I we factor
everything so so we can go from the
simplest solution so how can I make it a
little bit more future proof or prepare
for the optimization that I want to do
but without investing too much right now
because if you pass that point then you
were doing far too much the investment
is too big for the degree of uncertainty
that you have because you need to make
sure that is a guess that you're going
to do certain things so you need to
decide how much you're gonna invest
according to the certainty that you have
of what's coming next or it can come the
other way around it can come from right
to left you can say look there are
certain things that you need to have in
order to go to production or in order to
reach this milestone so but I'm Beauty
but this milestone or the release date
has all these features that you need to
build so how can we keep our solutions
simple as we go through each feature
without compromising what we need to
achieve so we can go the other way
around so once you start rushing
aligning that so the discussions between
you and your peers is not about a
solution anymore you can mark that now
why does one person want a design and
the other one wants another designer how
complex are going to make right now so
you can use that to find this inflection
point that can be here can be here can
be anywhere so I of course I'm away over
time there's a ton of stuff that I won't
cover
what's with the message here the message
is like test-driven development is a
phenomenal tool is the thing that I
started doing almost 14 years ago
and I never never looked back I
absolutely adore the rhythm that
discipline specify what you want make it
work make it better
the design aspect of it i chair epeak
sometimes i design a little bit more
upfront sometimes I let it emerge
according to the degree of confidence
that I have in the solution that I'm
building so I believe
we believe that if you learn how to
design well TDD will be much easier if
you understand there are different
styles and you have a good foundation of
design synergy will be easy
thank you very much
[Applause]
[Music]
Посмотреть больше похожих видео
TDD Isn't Hard, It's Something Else...
Agile Process - Georgia Tech - Software Development Process
Max Pronko | Pronko Consulting | 10 Pitfalls with Test Driven Development for Magento teams
ISTQB Agile Tester #39 - What is Test Driven Development (TDD) in Agile
Why Jony Ive is a great designer | Lex Fridman Podcast Clips
TECHTALK: Test Driven Development (TDD) Restful API dengan Mock Server Postman
5.0 / 5 (0 votes)