Transcript
This transcript was autogenerated. To make changes, submit a PR.
Hello everyone, and thanks for joining my talk. Today I'll be speaking about the
complex relationship between software developers and test code.
I'm also going to suggest and mainly demonstrate with live coding,
how we as an industry can shift some of our assumptions and methodologies
toward improving that relationship. Let us begin.
My name Tal Doron. I'm a software engineer currently
working at Nice and actually have been doing so for the past decade
or so. Mainly I've been working with backend technologies such as Java
net and of course node. About three and a half years ago,
I've stepped out of the development teams and assumed the position of a technical coach,
which means I go through almost every team in our R D organization
and teach a training which is called ASE, which stands for
agile software engineering, which basically covers in a very hands
on and intense way, all the practices that we can see on the right,
such as emergent design, coding, code, refactoring design principles and pattern
testsuse code. Valuable test, TDD and BDD.
Today we're actually going to focus on testable code,
valuable tests and TDD. Now,
after I teach the training, I usually join the teams and work with them
hands on, on their own backlog. And this way we can see that we actually
implement the things that we've done in class. So I
want to start with the following question, okay, and that's
a question for you, so try to think about your answer. How would
you describe your feelings regarding writing automated tests? It doesn't matter
if it's unit, component or integration test. Okay? Now this question actually
interested me quite a lot when I prepared this session. So I've created
the Google forms and published it in many, many forums
and I got several hundred answers. Okay? So think about your answer.
And of course, this is a multiple choice question. So these are the
options. A, I actually like it. B, I don't like it, but I understand
the value of me writing them. C, I hate it. I don't
write tests. It shouldn't be within the responsibility of the developers.
Now, after several hundreds of responses, the graph shows as follows.
Now this is a very surprising graph, so let's talk about it. About 45%
of people say that they actually like writing tests,
okay? 50% don't like it, but I understand the value,
and about 5% actually hate it. Now this is very surprising,
okay? I was expecting different numbers altogether.
So the first time I saw the graph, I thought to myself, you know what,
no chance. Only 75% actually values
the truth and 25% are straight up liars. Now,
the graph on the right, of course, is a joke. But I would expect about
80 or 90% of developers to say that they don't like writing tests, but they
understand the value of it and not like almost 50 50.
Now, I would say that it is a bit biased and the reason for
that is people that actually want to take a survey about testing
probably have some affinity for testing, right? And this affinity actually
has some effects on their response. So this is my guess.
But even if not even if these numbers are correct,
still as an industry, we have a problem. The problem
is that about 55% of developers say that
they don't like writing tests. Now, it's okay not to like something,
but if we don't like something, that we actually do
quite a lot. And most organization actually spend
a great deal of time today in writing tests and increasing their quality.
So if we do it quite a lot and we don't like it, so we
tend to do it bad. So where's the
love? So I would say that there are two main things that
affect this relationship. On the left, the afterthought effect, and on
the right, the univ effect. Let's start from the afterthought effect.
So this illustration shows a daily stand up. And I actually
remember myself participating in
a daily and saying the following. I've finished, okay, the feature is
working because I've tested it in some way, but now I have only tests
left, left to right. And you probably can relate. Okay?
When I think about it today, it's actually pretty absurd.
Why? Because it makes me a bit angry.
Why? Because usually when I write tests, after I write the code,
two things happen. First, I need to rework my code. Perhaps I
discovered that the code is not that testable, that I've missed some of the things,
okay? And I need to rework on something that I actually already know that it's
working. Second thing that happens when I write the test after the
code. So the test code actually becomes a second class citizen,
which means that I treat it not as good as
I treat the production code, okay? I do not adhere
to design principle. The code is not very clean and the code
becomes not maintainable. Now, if the test
code is hard to maintain and hard to develop on top of,
so my entire code base actually becomes this way. Okay? So this
is a very important point. The test code should not be a second cases citizen.
Another thing that the afterthought effect causes is the following diagram.
Okay? We are always stressed
out, right? We always need to deliver more and more value. We always encounter
bugs that we need to fix and we always need to attend many different aspects
of the application. Right? Now, sometimes we are so stressed
out that we actually say, you know what, we don't have time to test,
okay? We have to deliver something we don't have time to test. By the way,
even if testing is part of the definition of done, sometimes the hard
deadline is stronger, okay? Now, having no time to
test has its effects, right? It causes software
defects. If I do not test my software, I probably am missing something,
right? And these defects actually cause a
direct link to load and stress, okay? And this is a very vicious
cycle and I want to avoid it in some way. Now, another thing that it
causes if my test suite is incomplete because I don't have time to test
from time to time, so my trust in the test suite actually decreases.
Okay? Now let's talk about the unit effect. Now, the unit effect
has its own downside. Now, when people are asked,
what are unit tests? So they usually say, we need to test for unit of
code, the smallest units of code possible, such as a function, a module,
class, whatever, okay? And if a discussion cases.
So usually people talk about the testing pyramid that you probably know.
And this testing pyramid is a bit problematic because
it says several things. First of all, as you can see in the base of
the pyramid, we have unit test, right? So it means that according to
this arrow here, we actually need to have lots
and lots and lots and lots of unit tests to test for small units
of code, okay? Another thing that we can see from the error on the
right, we need to test these units of code in isolation,
okay? Now it looks okay, but it actually adds
like four other angry emojis to the picture. And I want to
talk about them first. If I need to test lots of units of
code in isolation, I need to spend lots of my time in writing mock
objects, right? Because this is the way that we can isolate. And nobody
likes to spend lots of time writing mock objects, right? They are not real.
Second thing, the quantity of
test rises. Imagine a complex application that has lots of
modules and lots of functions and perhaps lots of classes. It doesn't matter,
okay? If we need to test for every piece of code,
so the quantity of test becomes really massive.
Now the problem with quantity of test that is massive. The testing
for each unit of code is that if I want to refactor my code for
some reason, and I always want to refactor my code in order to adhere to
a better design, in order to meet a new requirement, it doesn't matter.
So refactoring becomes something which is known as refactoring hell.
Okay, we can be in two different scenarios. Okay, we can
say that, okay, we need to change the system. So we need to refactor the
code in order to better meet the requires. I can say the following.
I can say, you know what? I'm going to refactor my code and everything is
going to be okay because I have a safety net of tests that this
is a great place to be in. The second scenario can be,
you know what, there's no way that I'm refactoring my code
because I'm going to have to change about three or 400 tests,
okay? And of course, this is not the place that we want to be in.
Okay? Now, another problem with unit tests and with treating,
when testing for small units of code, when the test is very aware of
the code structure, is that actually the unit
test might pass and we can be very happy with it. But sometimes
when we integrate the system, so things start to break because everybody knows
that integrations are pretty painful, right?
So this gives me actually another decrease in my trust
in the entire test suite. So I would say that these all angry emojis.
Here are some of the main reasons the developers actually don't
like writing tests. So comes along
Kent back about 20 years ago or more and
says, you know what? There are actually two simple rules in order to solve that
problem. First, you write new code only if an automated test had failed.
Okay? And second, eliminate duplication.
Refactor the code if needed, before you continue.
Okay? He illustrates this mantra,
which is the red green refactor mantra that we can see here. And red
is saying, you know what? When you start to develop something. So please write an
example. Okay? Write a test that failed or even doesn't compile green.
Make only enough code for it to pass. Okay? Committing, by the
way, all the scenes necessary on the process. Green is not the phase
where I think about the best design or the best code that I can after
the test is green, so I can refactor my code. This is where I think
about the best design. I refactor my code. I run the test again to see
that I'm still green. Okay? And there's a logic behind this circle, okay?
I want to provide an example to what I'm about to develop.
Then I make it pass because it's really hard to imagine what
would be the best code to meet the problem. I want to think about the
algorithm and about the thing that I need in order to solve the problem.
And when the code is already written, then I want to try and think about
it. Okay? This actually works much better. Okay? So when
I refactor my code on the last phase, so this is where I think about
the best design. I refactor it, and if I went from green to green,
so I know that I should be good. Okay, great.
So I want to add three additional things to that. Okay? First of all,
a unit should be treated as a business flow, okay? The test should not
care about the structure of code. This is very, very important.
Okay? If the test doesn't know about the structure of code, the code can
be refactored as much as we want, and we always want to
be able to refactor our code, okay? A mock object is
only for external boundaries of the application. This is very important.
Okay? There is no rule of unit testing and then isolating everything.
The rule only says that I want to have valuable tests that I can easily
run and set up, okay? So if I want to mock something, it should be
an external boundary of the application, and there are also rules for that. Okay?
What are external boundaries? Databases, I o devices,
streams, other services. It doesn't matter. Okay. But I never
try to mock an internal layer of my application. Okay?
And that's important. Last thing. Last rule, which is super important.
The test code is as important as production code, and this
requires some shift of mindset for many, many people. Many people tend to
allow many, like, code scenes to appear
because it's test code and it doesn't matter. Okay? But when the test
suite becomes something which is very hard to maintain,
hard to understand, and hard to develop on top of, so practically
the entire code base becomes the same. Okay? So tests code
is as important as production code. And I apply every rule I know
about design or clean code and everything that I know on the test code
as well as production code. Okay?
Now, ive talked about TDD. Now, in order to
demonstrate TDD, I want to do a
code kata. So if you're not familiar with the term kata, kata is a
word from Japanese which means form.
Now, if some of you have been practicing martial Arts or seen
movies, so it's actually from there, okay? So a
kata is a sequence of movement that a martial artist does
over and over and over again. And the idea is that you're doing
something over and over again until it becomes part of your muscle memory,
okay? And then, of course, if the real thing
will happen one day, so your body will know how to react.
So came along Dave Thomas and took
this idea to the programming world. And he called it
a codecata. So what's a codecata? A codecata is a
programming exercise, not that difficult that everybody
with some experience can solve. And the idea is to practice some techniques.
So there are cutters for TDD, and there are cutters for refactoring, and there are
cutters for design patterns, and there are cutters for introducing new features
or new languages or stuff like that. And today I want to do a very
famous kata, which is called the bowling cutter, which was introduced
by Bob Martin. So the bowling
kata is a cutter that actually tells us, please solve
an exercise that will know how to calculate the result of a
bowling game. Okay, now, for those who are not familiar with bowling,
I'm going to introduce the rules very quickly, of course, if you need to,
please refresh. Okay, so what's a bowling game? So a
bowling game actually has ten frames for each player. Okay.
And each frame has two rolls. Okay. We roll the ball actually twice.
Okay. Now we have a very special frame, which is the last one,
that we might have three rolls. Why? I'm going to explain in a second,
because in the last frame, we might have a spare strike, and then we have
an additional role in order to calculate the code. So how
do we calculate the score? So the score of each frame is actually the
sum of two rolls, which is pretty simple if I didn't say so.
There are ten pins and we need to drop the pins. Okay, so the sum
of two rolls, the sum of the pins dropped in two rolls. Great.
Now there's a special situation which is called the spare. A spare is when
I roll the ball twice and after two times, I've actually dropped
all ten pins. So this is good for me. Why? Because I
get a bonus for that frame and the bonus is calculated as
follows. Of course it's ten because I dropped ten pins. And the bonus
is the next roll. I mean, the pins that were dropped
in the next roll. Great. Another special case
is a strike. Strike means that I took the ball,
I rolled it. Okay. And in the first go, I've actually managed
to dropped all ten pins. Okay, so that's great. And the bonus for that
is even bigger. So how do I calculate the score for a strike?
It's actually ten plus the two next rolls.
And the two next rolls doesn't have to be in the same frame. If I've
done a strike and then another strike and another strike.
So the result for the first one, the score for the first strike
would be 30. Right? Because it's ten plus ten. Plus ten.
Great. Now the maximum roles in the game are
21 because 20 is
two times ten. Right? And I have an additional role in
the 10th frame if ive done a
sparrow strike or something like that. And the minimum would be eleven.
Why? Because imagine a game that I've rolled the balls nine
times, nine frames, and I've done a strike, okay? And let's imagine that in
the last frame I've actually missed twice, okay? So whenever I do a
strike in the frame, I don't have an additional roll because I've already managed to
pin to drop all ten pins. Okay?
And the maximum score of a game, of course, is 300. Why? Because in
the example that I just gave, if I'm doing strike and strike and strike and
strike over and over again, so eventually it will be
30 times ten, which is 300. Okay? And the minimum
score is of course zero. Right? Because I've rolled
the ball and I've missed every single time.
Okay, great. Now I hope that you're
starting to think about it. I mean, start to think if you would need to
implement it. I mean, how would you implement it? Okay,
and let us start. So our requirement is actually
to have two APIs, okay? One API would
be to have a role method
which gets the number of pins. So every time the number of pins is
dropped. So the role method will be called with the number of pins that were
dropped. And another one would be. Another API would be the score
method. And the code is actually being called only in the
end of the game. Okay, this is to
make the cut a bit easier. I don't need to know the score all the
time. Only in the end of the game, someone will call the score API and
will ask me to calculate coding to the roles that
were taken before. Okay? Now we're
not going to deal with turns like whose turn is it? And stuff like that.
Imagine if we have a module per player or something like that. Okay?
So let's go to the ide and start.
Now, I want to do it using TDD
because I think the TDD actually cancel
the afterthought effect, which we talked about in the presentation.
And also when I'm doing TDD and I'm thinking about business flow,
so it almost terminates the unit effect as well.
And I want to demonstrate how I do it. Okay, so I
have a project here. This project is pretty empty. It only has some configuration
for running tests using jest. Okay,
so let us begin. So the first test would be
what? So that's always something to think about.
I mean, the first test in TDD needs to describe something very
easy, right? I don't have any code written yet. Okay. So I
don't want to take the hardest scenario possible because
then I will need to implement lots of code in order to adhere to it.
Right. So I always try to choose the simplest scenario
possible that I can solve pretty quickly.
Okay, so what would be the most
simple scenario that we can think about in bowling? So in bowling, it would be
pretty simple, right. If every player everywhere would always miss,
always throw the ball into the gutter. So the score would always be
zero. Right. So let's do that. And as you can see, I'm not
testing for methods or for stuff like that. I don't have the structure of the
code yet. I am testing for business flows. So I
would say that it should score zero when playing
a gutter game.
Great. So what do I do first? I say Const.
Bowling equals require.
And I'm going to require bowling. Now, it doesn't exist
yet, so it's a bit strange, right? But still,
this is, TDD also promotes something which is called outside in programming,
meaning I imagine that I have something and then I create it. It has many,
many benefits and one of them is the fact that the code
becomes much more usable because the test is already using it. So it doesn't
exist. I create it. Great, let's go back here.
And now I want to say, okay, so let's run a for
loop 20 times and let's do bowling role.
And roll zero, right? This is one of the APIs. Now, red green refactor
means that if something doesn't exist or doesn't work, we can see this quickly line
here. So I need to create it. So I'm now in red and I want
to create it. So create method role, which tests the numbers of
pins, sorry, pins.
Okay, now I want to split my screen into two so
it will be convenient for us to see. So on the right I'm
going to have my production code. On the left I'm going to have my tests
code. Okay, so let's start from
here. Now what do I want to do? I want to say, okay, so let's
expect that if someone cases bowling score in the end
of the game, so the score should be zero.
Okay, now score doesn't exist, so let's create it.
So let's create it. That's great. And let's run the
test. So TDD is saying, okay, provide an example
for something, right? Perhaps it will not work or doesn't compile
and then run it. See that you're in red. And then we're going to make
it cases. So let's see what's happening here.
It takes some time. Okay,
so it didn't work. Why? Because expected zero, right? I expect it
to be zero and received undefined. So this is pretty clear, right? Because I return
undefined here. So of course it doesn't work. Now, what's the fastest thing I can
do in order to solve it? Okay. Without thinking about anything. So the
fastest thing to do would be returning zero. Let's run the test
now, you probably think that now, okay, but this
is pretty silly. Okay. Of course, if you return Zero and you expect zero,
it will work. But you didn't do anything. So I agree. And TDD
also agrees. But TDD also says, you know what, if it's
so silly. So let's provide an example to why return zero
is not enough. Okay, and how do we provide an example? We actually write
an additional test. So let's write an additional tests.
So this was very simple when playing a gutter game. So let's increase
complexity just a bit. So I would say that it should score 20
when I'm playing a game with all ones.
A game with all ones. Now,
imagine that I've rolled the ball and each time ive dropped one pin.
So of course the code would be 20. This is very simple. So let's
do that. So if I've rolled once and expected would be 20,
of course I'll run the tests and they will fail, right? Because I return
hard code zero here. So let's
try to make them pass. So first thing
that comes in mind. Okay, let's create a variable.
Let's call it current score. And it will
be zero. Now, whenever someone cases code, let's return
current score. And whenever someone calls roles, so let's calculate
current score. So I would say current score plus equals the number of pins,
right? And I want to change the order of the method because the
role is called before score, and it's better for me to look at it this
way. Okay, let's run the test.
So I think that's going to work. But I'm starting to think that I
might have an additional problem here. Okay, so the tests are green,
which is good. But do you remember that I told you that I don't want
to handle with turns of different people, right? I want to have a module
per player or something like that. And currently, because this
is a module, so it exists only once in memory. So if I'll write an
additional test here. And I'll probably do something like
that and say, you know what, let's see if it works again.
So I'm guessing it will fail, right, because it's already in memory count score after
this game would be 20 and here it should be 40.
So I'm not sure this design is actually good enough for me because I want
to be able to create this kind of module per player.
Okay. Yeah. So that's not good enough. So I want to go back to
green. Okay, let's run the test and I want to make a change.
Okay. So instead of using a module here, I will actually use
a class.
So let's do that. So class bowling,
okay. Class bowling would have what?
It would have a constructor and in the constructor I would have
this current code which
will be zero. Great. Now I'm going to take these two
function inside the class.
Great. And now of course I don't need the syntax of export function.
And I don't need this syntax of export function.
Great. And I actually don't need this let current score as well.
And I do need to do this current
score here. Right. And this current
score here.
Great. And I need to export the
module. So module export will be bowling.
And that's about it here. So this is kind of refactoring. But it's not only
refactoring because I'm changing a bit of behavior right now. I'm going to instantiate this
one and not only have it as a module, but still it's
the same behavior. So let's see. So here instead
of doing this requires, let's take this outside and let's change the
capital b. And I'm going to say, you know what,
let's let a bowling variable and let's create a before
each method, okay, the before each method is a method that
actually runs before each test. Okay. And before each
test I'm going to do bowling equals new bowling.
Okay. And now I don't need this bowling as well here.
And let's run the test to see if it's working now.
So I've done some refactoring here. So this is the idea of red green refactor.
I already have some code. I see some problem or something
that I don't like. I refactor it and see that it is still working.
So of course it's not working because yeast is not defined
naturally. Okay, sorry for that. Let's run it again.
It and once we're green, we know we're good to go.
Great. So we're green and we're good to go. So this is actually much better
for me. So red, green and refactor. So now I
should be refactoring, but I've already refactored my code. But that's a very important
point in TDD in the realm that we are living. So test
code is as important as production code, meaning whenever I'm doing refactoring,
it also goes to the test code. Okay? So I look at
the test code and I think how can I improve it? And actually I can,
right? Because this piece of code here is
actually duplicated for this piece of code. It looks exactly the same,
right? So I want to extract it into a function and use the
function, but I have some hard coded values here. So I'm going to do it
in two phases. First of all, I'm going to extract this 20 here and I'm
going to call it roles. I'm going to take this zero here, extract it
to a variable. I'm going to call it pins and extract it
out. Now I'm going to extract this into a method, okay.
And the method is going to be
role many. Okay, it's a helper
method, the test method. Now I actually don't need these variables, the roles
and pins, okay? I've just used them in order to help the ide help me
to do better refactoring. So I'm going to inline them back,
okay. I'm going to inline them back and I don't need them. And now I'm
going to take this line and I'm also going to invoke it in this test.
By the way, this version of Webstorm doesn't
recognize code application replace them by their own. But they have actually
promised that they will fix it soon. So I hope it will come soon.
Great. Let's run the test again.
Okay, let's wait. It takes a bit of time. Great.
We should be good. Finally we can continue. So we've
tested for a gutter game. We've tested for a game with all ones.
So what could be the next scenario? So as you can see, I'm not testing
full structure of code. Okay. So I would say that maybe
we could continue and test a game that has only one spell.
So it should actually calculate the
score correctly when
with one spell. Great. So how
do I simulate a spare? I do the following, I would say bowling roll.
And I want to roll, for example a seven. Okay.
And then I want to roll a three. Right. That would be a
spare because I've reached ten. And then let's roll an
additional three so I can actually calculate the spare bonus.
I remind you that the spare bonus is ten plus the next roll. Okay,
so I have 17 rolls
left to throw. I'm going to roll zero in all of them to make it
simple. Now, what's the expected score? So let's calculate.
Okay, so for the first frame, I would get ten plus bonus, which is ten
plus 313. For the second frame, I will have this three,
and of course, a zero that comes from here. So it's 13
plus three, which is 16. Okay. Try to calculate for
yourself before I explain it. It will help you see that you actually understand
the rules of the game. Great. Now will
that work? Of course it doesn't work. Okay, I'll run it in the background.
But it doesn't work, right? I mean, I don't have any ability to calculate the
spare bonus because I don't know what is the next role in my current
implementation. An additional thing that actually bugs me
here is the fact that if I look at the code, I'm not sure that
it's very good. I mean, I have a role method, a role function
that what it does, it actually calculates the score. So I'm not sure
this name is very good for the method. And I also have
a score method which actually returns the score.
It's kind of a getter for this current score variable. Right.
So I'm not sure that's good enough. I want to do some refactoring
here in order to meet the new requires, which will be calculating something
from the next frame. Right. So before
I do that TDD tests, you, you know what? You want to do some refactoring?
That's great. But before you do that, please start from green.
So I'm actually commenting out this test and I want to
start from green because whenever I'm starting to refactor, I want
to make sure that things are green. And okay. In this way I will know
that my refactoring didn't break anything and that's very important. Okay,
so now I'm green. I'm going to do this. So I'm going to say this
rolls will be an array. Okay. And whenever someone
is bowling something. So let's do this roll
and let's push the number of pins that were dropped.
Great. Now whenever I want to calculate the score, so let's create
a local variable code and I'm going to
iterate over my roles.
Great. And now I'm going to do score
plus equals roles in I.
This should work. Of course it should be this roles.
Okay. And instead of returning current score, I'm going to
return the local variable score. I think that
should work. And also we keep the history and
the future by doing this roles. Push. Great. Now I don't
need this current score. I don't need this current score as well. Let's see if
I didn't break anything that's important. I've started from green.
I want to see that I'm still green. Let's see.
Okay, great. Still green. Now I
allow myself to put the test here again. Now it's going
to be red. Of course I didn't do anything, but how would I implement it
now? So the very basic thing to do would
be, okay, so if this dot rolls
in, I plus this dot rolls in I plus
one. If it's already ten, so what might happen?
So did you catch the error here? There was an error here
actually. Right. Because I might be checking the
second role in a specific frame and the first role in the
next one. Okay. And this is not a spare. So this is still not
good enough. So my design is still not good enough. I'm not sure that
iterating over roles is the right thing to do. So whenever my design is still
not good enough. So still I want to start from green. Okay.
Commenting out the tests, I know that I'm green now and I want to do
something a bit differently. I want to say, you know what, let's have another
one, which will be the role index.
And I want to iterate over, instead of iterating over the roles,
I want to iterate in the context of frames. So instead of doing that,
I'm going to iterate for ten times. Okay. And by doing
that, I'm going to say the score should plus equals this roles in
current role, sorry, in role index plus
this roles in role index plus
one. And then I'm going to take this index and increase
it by two. And this way I know that I'm in the context of
frames. So let's see if that's working. I want to make sure that
I've started from green and I'm going to be green again.
Okay.
And then I think that now I will be able to add the functionality.
Okay, so I'm still green. Finally adding this test.
Okay. I will run it to see that. Of course I'm still red. Okay.
And now I think that I know how to solve this
challenge. So I would say, okay, I'm going to start writing while
the test runs because I want to make sure that we're
continuing in the correct pace. So of course it doesn't
work. So I would say now if this dot rolls in
all index plus, this dot rolls in
all index plus one. And now I actually know that I'm in the
context of the same frame, right? So if this happens,
so score should be calculated as follows.
It's ten plus these dot roles in roll
index plus two. Right, because it's the next role.
Great. Else I
calculate it regularly. Okay.
And either way, I need to increase the role index by
two. Let's run the test to see if it works.
Okay. Let's see if we're finally green. And we're finally green. Okay?
So that makes me happy. But now let's take a
look at the code. I mean, the code actually becomes pretty complex. So I remind
you, red, green, refactor. So now I should refactor my code.
This is complicated and this is complicated. And if I need to read it and
understand what's going on, it's not that trivial. So someone would
say, you know what, this if is very long. So you need to comment something.
You need to said, okay? Check if this is spare.
Right? So this is an ugly comment.
Okay. Comments are actually not that good practice.
Okay? We need to comment things only that we are unable to express via
readable code. And here this is not the case. So instead of this comment,
I would actually take this entire condition, extract it
to its own method, and I'm going to call it is spell.
Now it's much more readable. Right. Another thing that I'm going
to do, I don't want to work with indices here. Okay? There's a clean code
rule that actually says keep the same level of obstruction in your
function. So I'm going to go here and I'm going to extract it out as
well. And I'm going to call it this spell
bonus. Right? This is how it's calculated.
And again here I'm going to take this line and I'm
going to say let's extract it out and call it sum of two
roles. Great.
So now actually the code is much better when I read it. It's very readable
and very easy to understand. Great. Let's run the test to
see that I didn't break anything. Now try to think about what would
be the next test. Okay, if this is going to be green, I've tested for
a gutter game. I've tested for a game with all ones. And I've actually tested
for it with a game with one spell. So what would be the next phase?
So I'm guessing some of you said it would be a strike.
Okay, so let's see how we can do that.
So it should calculate the correct the score correctly with one strike.
Great. So how do I simulate strike? So I'm
going to do this, right. On the first roll, I've managed to drop
all ten pins, which is great. And now let's keep these three and three because
I need both of them in order to have the strike bonus. I remind
you, a strike bonus is the next two rolls. Okay.
And then I'm going to roll a gutter game
until the end. So how many rolls do I have left? It's actually not
17. It's going to be 16. Right. Because this ten
actually counts as two. Because on the first frame, when I did the strike,
so I didn't have an additional role. Right. So it counts as two. So two
here and two here. So 20 minus four. It's actually 16.
What's the expected score? Try to think for yourself.
Okay. Before I let you know. So it's going to be 22,
right? How? Because on the first frame, I get ten plus
the strike bonus, which will be the next two rolls. So it's three plus
three. So ten plus three plus three. It's 16.
Right. On the second frame, I have three plus three, which is six.
So 16 plus six would be 22.
Okay, great. So let's
continue. I think now it should be pretty obvious,
right. I'm going to go here for my iteration. I'm going to say, you know
what? If this dot rolls is already in,
the role index is already ten.
So what should I do? Sorry, not if. So what
should I do? I should say that the score
plus equals ten plus this dot roles
where in the role index plus one. Right. This is
the next one plus this dot roles
in role index plus two.
And now what do I need to do? Now this is a bit tricky because
the role index actually need to be increased only by one. Right.
Because I do not get to have a second role in this frame because I've
done a strike. Okay. And of course I should add an
else here. And this role index plus equals two should
now go inside the cases.
Okay, let's run the test.
So this red green refactor thing actually causes me to
refactor my code over and over again. So I'm always keeping my
code tight and clean. And of course, I'm doing refactoring for the
test code, as I showed you in the beginning. So the test code also becomes
quite clean. Great, so this is working. So refactoring, we already know
the tricks here, right? I mean, we don't like these heavy conditions.
So I'm going to extract them out. I'm going to say let's
call them cases. Strike.
Right? And I'm going to take this code here and I'm
going to say, this is pretty complicated. Let's call it strike
bonus. Now there's an additional
thing that I want to do. Now, you all understand that I'm iterating
in the context of frames, right? Because it's ten and it's pretty obvious.
Okay? But there's another thing that I can
do that will really help. I mean, instead of calling this variable,
I, I would change its name and I'm
going to call it frame. And now everything is understood.
Now we tend to always call I or j or k in the indices
of a loop in case we are using this kind of for loop.
But sometimes when we change the variable into something meaningful,
it really, really helps. And I really, really like it this way.
Okay, so let's run the test.
Now when I'm running a test, try to think how do we continue from
here? I mean, this is not that obvious, okay, because now
we have four tests. All of them actually cover the basics,
right? And what do we do from now? Because we can do many, many things,
right? We can actually say, let's run this kind
of crazy game that has everything, right?
Gutter, regular, spare strikes, everything.
We can write a game with all spells. We can test for a game with
all strikes. We can do a combination. We can do many, many things. Okay,
so let's start from an edge case. Okay, let's do a perfect game.
Okay. A perfect game is a game with all strikes. It will also cover
the fact that we are doing strike in the last frame, that we
need to have three rolls or something like that. Okay, so let's do that.
So I'm going to duplicate this test and I'm going to say that
it should actually score 300 when
playing a perfect game.
So how do I simulate a perfect game? So I'm going to use my roll
many method. Okay, I'm going to roll twelve
times because this is how I can do it when
I have strike every time and I'm going to strike each time.
Okay, which is great. And the expected, of course, should be 300.
Now try to think, will that work or not?
What do you think? Some of you are saying
yes and some of you are saying no, I'm guessing, but let's try. Okay,
I'm going to run this test.
What is suspense? Let's see. Okay,
already working. Interesting. So I've added a new
test, but I didn't need to change any piece of code in order to make
it pass. So that's interesting. What would you
test next? Okay, so it's starting to get complicated. Should I test
a game with all spells? Should they create a combination or
something? Now I'll let you in on a
small secret. Now we're basically finished, okay. The small
piece of code that we've just written here is actually what we need
in order to calculate a bowling game. So we're done. Every test that we're
going to add here is going to pass. Okay. Which actually brings
me to a question. Should I add more tests if
I don't need to change any code, so should I add more tests? And it's
a difficult question because people sometimes tend to say, why not?
I mean, add some tests and then you will have a safety net, but that's
not very accurate because every piece of code that I write doesn't
matter. Test of production I actually need to maintain later on.
So I want my test suite to be small but very powerful.
Okay. So this is one
thing, but the second thing is that, okay. But I can't rely only on
the basic cases. So I actually like the fact that I've added this perfect game
here. Okay. Because it's kind of an edge case. It's kind of a
thing that helps me know that I've tests for most scenarios. So in the real
world, maybe I would add something like a sanity test that will
take some combination to see that things are working, but that's about it. Okay.
I'm very careful with adding more and more tests. Now, what do we think about
the production code? I mean, if you imagine something when ive
read the requirements, was it this way or was it something else? I'm not
sure. But I actually like the production code. I think it's very concise.
It's very, very expressive. Okay. And very readable. Now, some people
tend to say that if else if is not very good because it is
not closed for changes, if you know the solid principles. But I
would say that the bowling game has never changed, at least
not that I know of. Okay. So when the number of possibilities is
closed, so we're actually good. So we're basically finished the exercise.
So I want to summarize several things.
Okay, so first of all,
I want to thank you for participating, and I hope you find it useful and
interesting. Okay, if you did, and I hope you did, I highly
recommend on trying out the things that we have discussed on your project. It can
be test first if it's not test first. So try to go for business flows
instead of unit test. Okay. Try not to mock everything.
Okay. But if you find it too difficult, especially with test first.
Okay. So I highly recommend on practicing on code cutters such as the
one that I've just did. Okay. It's very useful and very effective,
and the effect of muscle memory actually works when you go
later on to production. It really helps.
Now, thank you very much and feel free to contact me on any platform.
Thanks again.