Conf42 JavaScript 2021 - Online

I've finished, it's working... only tests left..!?

Video size:

Abstract

I remember saying it over and over again, and always feeling like something is just not right. “I’ve finished, it’s working.. only tests left”. Why do I write tests if the task is finished and works as intended? And why is it considered to be a pain to many developers?

In this session I’ll be showing the solution to the conflict above, which requires a shift of mindset towards TDD. I’ll be live coding the famous Bowling Kata while letting the tests(use cases) drive my design and implementation so that once the code is written and working, the task is actually done.

Summary

  • Tal Doron: 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. Today we're actually going to focus on testable code, valuable tests and TDD.
  • About 55% of developers say that they don't like writing tests. There are two main things that affect this relationship. The afterthought effect and the univ effect. Testing should not be a second cases citizen.
  • The unit effect has its own downside. Usually people talk about the testing pyramid that you probably know. This testing pyramid is a bit problematic because it says several things. It actually adds like four other angry emojis to the picture.
  • If I need to test lots of units of code in isolation, I should spend lots of my time in writing mock objects. Second thing, the quantity of test rises. Refactor the code if needed, before you continue. Here are some of the main reasons developers don't like writing tests.
  • In order to demonstrate TDD, I want to do a code kata. A kata is a sequence of movement that a martial artist does over and over again. Dave Thomas took this idea to the programming world. The bowling kata tells us how to calculate the result of a bowling game.
  • Using TDD, we can test for business flows. The first test in TDD needs to describe something very easy, right? 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. Here's how we test.
  • Test code is as important as production code in TDD, meaning whenever I'm doing refactoring. Instead of using a module here, I will actually use a class. This is the idea of red green refactor.
  • Webstorm doesn't recognize code application replace them by their own. They have actually promised that they will fix it soon. I want to do some refactoring here in order to meet the new requires, which will be calculating something from the next frame.
  • When doing TDD tests, please start from green. In this way I will know that my refactoring didn't break anything. Instead of iterating over roles, I want to iterate in the context of frames. Let's see if that's working.
  • Check if this is spare. Comments are actually not that good practice. We need to comment things only that we are unable to express via readable code. Instead of this comment, I would actually take this entire condition, extract it to its own method. Now it's much more readable.
  • Refactoring causes me to refactor my code over and over again. I'm always keeping my code tight and clean. Every test that we're going to add here is going to pass. Should I add more tests if I don't need to change any code?
  • I think it's very concise. It's very, very expressive. And very readable. So when the number of possibilities is closed, so we're actually good. But I would say that the bowling game has never changed, at least not that I know of.
  • I highly recommend on practicing on code cutters such as the one that I've just did. The effect of muscle memory actually works when you go later on to production. Try to go for business flows instead of unit test. Feel free to contact me on any platform.

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.
...

Tal Doron

Principal Software Engineer @ NICE

Tal Doron's LinkedIn account Tal Doron's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways