From the course: Unit Testing and Test Driven Development in Python

Example TDD session: The FizzBuzz Kata - Python Tutorial

From the course: Unit Testing and Test Driven Development in Python

Example TDD session: The FizzBuzz Kata

- [Instructor] In this lecture we're going to walk through an actual test driven development coding session. For the example we'll be using the FizzBuzz code kata. The name kata comes from martial arts and means training exercises. So code kata are training exercises for programmers. The FizzBuzz kata is pretty simple. We'll be implementing a function that is passed in an integer value as an input, and the function will return the string Fizz if the passed in number is a multiple of three, Buzz if the passed in number is a multiple of five, and FizzBuzz if the passed in number is a multiple of three and five. If the value is not a multiple of three or five then the value itself is returned as a string. I'll be using PyCharm as my IDE for this example and in all the example coding sessions for this course. But the TDD process should apply regardless of the IDE you're using. In some of the future lectures I'll show you how to build and use pytest and some of the other common IDEs as well. Okay, so let's get started. On the screen I've got a basic test that I can use just to verify that things execute properly. This tells me that I've got pytest set up in my project correctly and I'm ready to start writing tests. PyCharm comes with a pytest test runner installed and that's what you see here at the bottom of the screen. The test runner shows all the tests that were executed and if they passed or failed. The screen bar on the right-hand side shows if all the tests passed or not. When it's green then all the tests pass. If any of the tests fails this bar will turn red. This is a typical layout for a test runner in an xUnit type framework. In this file in the right-hand tab I've got the list of test cases I want to implement. These should be in order of increasing complexity from the simplest test case to the most complex. There are more complex test cases that can be added, but I think this is enough for this demonstration. Okay, so let's implement our first test case. Can I call FizzBuzz? To start, I'm going to enter the red phase, and in the red phase I need to implement a failing unit test. So I'm going to repurpose this assert True test case to validate that I can call the FizzBuzz function. Now I have an error because the FizzBuzz function does not exist, and per our three laws of TDD not compiling is equivalent to failing. Although with Python it's not really compiling. So I've successfully implemented a failing unit test, which means I've completed the red phase. Now I move onto the green phase. I need to make this test pass. I can do that pretty simply by implementing the FizzBuzz function. This shouldn't be the final implementation of the function. This should just be enough to make this test pass. I made the unit test pass with the simplest change I could make to the production code. We're done with the green phase and it's time to go on to the refactor phase. Is there anything to refactor here? I don't think so. So I'm going to mark this as complete in the to-do list and then go ahead and move right back to the red phase and restart the TDD cycle. Next test case. I need a string one return when I pass in a value of one. I'm in the red phase so my first step is to implement a failing unit test for this functionality. Now that I've got a failing unit test I go to the green phase and make the test pass. To make this test pass I'll need to make the simplest change I can to the production code that will make the test pass and not break the other test. That should be pretty easy in this case. I'll just modify the code to always return a string of one. There we go. All the tests passed now. On to the refactoring phase. Is there anything here to refactor? Is there any duplication? There is. This call to FizzBuzz is duplicated in both of my test cases. Hat first tests case is no longer needed as the second test case validates that FizzBuzz can be called. I gonna go ahead and remove that first test case. Okay, I don't think there's anymore refactoring to be done. I'll mark this test case complete and then back to the red phase in the next test case. In this test case I need to return a string of two when a value of two is passed in. I'm in the red phase, so the first thing I'm going to do is implement a failing unit test. Now that I've got the failing unit test I go to the green phase and update the production code to make the test pass. I'll make this test pass by generalizing the production code. I'll make it more generalized by converting the passed in value to a string. This is a common occurrence with TDD. You'll implement some functionality in a very specific way in one test case and then generalize it in other test cases. Now that the test is passing I can go on to the refactoring phase. Now in the refactoring phase I need to look for any duplication or other cleanup in the code. Is there any duplication here? The structure between the two tests is duplicated. I'll refactor that into a simple utility function that can be used by the test. Great, I'll mark off this test case and move onto the next. In this test case things are starting to get interesting. I'm gonna make the code return Fizz when a value of three is passed in. Again, I'm in the red phase so my first step is to implement a failing unit test. I'll use the new utility function to implement it. Now that I have a failing unit test I'll move onto the green phase and make it pass. I want to make the smallest and simplest change I can to the production code to make this test pass. That can be very specific for this test case, and then I'll generalize it as necessary in the future test cases. So for this test case I'm going to update the production code to check if three is the passed in value, and if so return Fizz. Okay, the test is passing. Now I'm gonna move onto the refactoring phase. Is there anything to refactor? I don't think so. So I'll mark this test case complete and then back to the red phase to enter another iteration of the TDD cycle. This test case is very similar to the last test case. Make FizzBuzz return Buzz when the value five is passed in. First I'll implement the failing unit test. Okay, now I'm in the green phase, and I'll make the test pass by adding another if condition to the production code. Easy. Okay, now I go to the refactoring phase. Is there anything to refactor? It doesn't look like it. On to the next test case. In this test case I want to generalize the production code to not just return Fizz for the value of three, but three or any multiple of three. I'm in the red phase, so I'll write the failing unit test. I have a failing unit test. Now I'll go to the green phase and make it pass. I going to modify the if statement for the three to have it use the modulus operator to see if the remainder of three is zero. Great. Now I'll refactor. I think this FizzBuzz function could use a little cleanup to make those if statements cleaner. I also don't like the multiple return statements. But I think I'm gonna leave them for now. So let's move onto the next test case. This test case is basically a repeat of the previous test case, but this time I want to return Buzz for any multiple of five. First I'll implement a failing unit test. And now I'll make it pass like I did before using the modulus operator. This is getting to be routine. Okay, now I'll refactor. Is there anything to refactor here? Any duplication? The expression and the two if statements in the FizzBuzz function are pretty much identical. I'm gonna go ahead and extract them into an is multiple utility method. And all the tests still pass. On to the last test case. This final test case is to return FizzBuzz if the passed in value is a multiple of three and five. I'm in the red phase so I'll implement the failing unit test. I have a failing unit test. Now I'll go onto the green phase and make it pass. I'm going to make this pass pretty simply by adding another if statement to the FizzBuzz function, checking if the value is a multiple of three and five. Excellent. The code is passing all the test cases. On to the refactoring phase. Anything to refactor here? I think this looks pretty good and no more refactoring is really necessary. So that completes the FizzBuzz code kata. In the exercises for the course I'll provide some additions you can add to this kata to make it more challenging. In the next lecture I'll go over downloading pytests and getting it set up in PyCharm with a built-in unit test runner.

Contents