[PYTHON] Getting Started with TDD with Cyber-dojo at MobPro

Introduction

This is the article on the 10th day of Inatatsu Adventar.

Continuing from the last time, this time is the participation note of Do Mob Programming in Kansai ~ Hin ??.

This time, I will write what TDD looks like based on the facts (slightly modified) the flow when I actually performed TDD for the first time.

For the first time

Start pytest, FizzBuzz in the same way as previous article. スクリーンショット 2019-12-09 23.38.54.png  If you test it and get angry, then implement it so that you don't get angry.

スクリーンショット 2019-12-09 23.41.29.png

The test passed and it became Green. Up to this point, it is the same as last time.

You have now confirmed that the test passes if it meets your requirements.

Try fizzbuzz

Now that the hiker is obsolete, let's rewrite hiker.py and test_hiker.py to fizzbuzz to meet this request. スクリーンショット 2019-12-10 13.38.00.png

There is no module like hiker! I was angry. Let's rewrite it according to various fizzbuzz.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

test_fizzbuzz.py


import fizzbuzz


def test_life_the_universe_and_everything():
    '''a simple example to start you off'''
    douglas = fizzbuzz.FizzBuzz()
    assert douglas.answer() == 42

Run the test as. スクリーンショット 2019-12-10 13.40.16.png The test was successful! !! This is a joyous discovery of the century (exaggerated), and everyone is happy to feel happy for the time being.

The state has changed from Red to Green, so next is Refactoring.

For the time being, let's make it easy to delete the comments that are from the beginning. And I also cleaned up the import area of the test code.

test_fizzbuzz.py


from fizzbuzz import FizzBuzz


def test_life_the_universe_and_everything():
    douglas = FizzBuzz()
    assert douglas.answer() == 42

The program has been refactored a little. Now that we have rewritten the program, let's test it. It would be a problem if the tests that passed were not passed. スクリーンショット 2019-12-10 13.46.26.png You have successfully passed the test code.

FizzBuzz started! !!

Now that I understand the flow of TDD, I will proceed with FizzBuzz. First, let's break down the FizzBuzz requirements into appropriate particles.

What is the smallest scenario for FizzBuzz?

To think about the smallest FizzBuzz scenario, first identify the elements needed for FizzBuzz.

--When the argument is 1, the return value is 1. --When the argument is 2, the return value is 2. --When the argument is 3, the return value is Fizz --When the argument is 5, the return value is Buzz --When the argument is 6, the return value is Fizz --When the argument is 10, the return value is Buzz --When the argument is 15, the return value is FizzBuzz --When the argument is 30, the return value is FizzBuzz

Is it something like this? For the smallest scenario, this time, the top "return value is 1 when the argument is 1".

Originally, we should separate and identify the requirements in this way, but since it was the first time, we omitted it.

Test and implementation with a return value of 1 when the argument is 1

TDD first writes test code. For the method that does fizzbuzz, let's test the return value for the argument with fizzbuzz.

test_fizzbuzz.py


from fizzbuzz import FizzBuzz


def test_life_the_universe_and_everything():
    douglas = FizzBuzz()
    assert douglas.answer() == 42

def test_When the argument is 1, the return value is 1.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(1) == "1"

When I run the test, there is no method for fizzbuzz! I'm angry, but I'm looking forward to getting angry and press the test.

It happened. As expected. スクリーンショット 2019-12-10 14.00.23.png

Now let's implement fizzbuzz.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(i):
        return "1"

This will pass the test! Alright, it's a test! !!

スクリーンショット 2019-12-10 14.11.33.png

Well, I got an error saying that the number of arguments is strange.

Let's go around.

Apparently, you have to hand over yourself. It seems to be customarily named self.

Now let's modify the program.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, i):
        return '1'

If you look closely, you can see that the answer argument also has self. Following this, self was given as an argument.

スクリーンショット 2019-12-10 14.14.01.png

Oh, this passed the test.

You have successfully cleared "Return value is 1 when the argument is 1". Refactoring is a mixture of double and single quotes, so let's unify it to single quotes.

Now that the minimum scenario has been successfully cleared, it's delivered! !!

No, well, I've only tested at 1, so I think it's okay, but let's do a test at 2, so let's test at 2.

When the argument is 2, the return value is 2.

As before, write the test for case 2.

test_fizzbuzz.py


from fizzbuzz import FizzBuzz


def test_life_the_universe_and_everything():
    douglas = FizzBuzz()
    assert douglas.answer() == 42

def test_When the argument is 1, the return value is 1.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(1) == '1'
    
    def test_When the argument is 2, the return value is 2.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(2) == '2'

Something like this. Added a test to return 2 when 2. Alright, let's test. スクリーンショット 2019-12-10 14.22.52.png

It's an error. I'm glad I didn't deliver it as it is ...

The error content is that I want 2 but I don't want to move in.

I was troubled. What to do now? A wise programmer said here. "Let's return the argument as it is" The typist rewrites the code as he is told.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, i):
        return i

スクリーンショット 2019-12-10 14.26.17.png Oops? I got two errors. Since I was checking the match between the character string and the number, I will convert the argument to a character string and return it for the time being.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, i):
        return str(i)

Let's test it.

スクリーンショット 2019-12-10 15.43.12.png The test was successful. Let's be very happy.

--When the argument is 1, the return value is 1. --When the argument is 2, the return value is 2.

I was able to clear the two items.

It's refactoring.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        return str(arg)

The argument i is confusing, so let's call it arg.

When the argument is 3, the return value is Fizz

Now it's time to write a test and check for errors.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg == 3):
            return 'Fizz'
        return str(arg)

test_fizzbuzz.py


from fizzbuzz import FizzBuzz


def test_life_the_universe_and_everything():
    douglas = FizzBuzz()
    assert douglas.answer() == 42

def test_When the argument is 1, the return value is 1.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(1) == '1'
    
def test_When the argument is 2, the return value is 2.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(2) == '2'
    
def test_When the argument is 3, the return value is Fizz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(3) == 'Fizz'

This worked fine in case 3. スクリーンショット 2019-12-10 16.59.37.png

Now let's check the specifications.

readme.txt


Write a program that prints the numbers from 1 to 100.
But for multiples of three print "Fizz" instead of the
number and for the multiples of five print "Buzz". For
numbers which are multiples of both three and five
print "FizzBuzz".

Sample output:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
... etc up to 100

Even in the case of 6, if Fizz is displayed, it is written in the specifications.

Let's create and test a test when the argument is 6 and the return value is Fizz. スクリーンショット 2019-12-10 18.29.37.png

6 is the return value. Do not do that.

Consider the specifications.

It turns out that I want to display Fizz when it is divisible by 3.

And Mob says, "Please rewrite the if statement so that it is displayed as Fizz when the remainder of 3 is 0." Typists implement like machines.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 3 == 0):
            return 'Fizz'
        return str(arg)

It has been rewritten on the condition of the remainder.

スクリーンショット 2019-12-10 18.40.36.png

I was able to safely display fizz when it was a multiple of 3.

Let's take a look at the current test code.

test_fizzbuzz.py


from fizzbuzz import FizzBuzz


def test_life_the_universe_and_everything():
    douglas = FizzBuzz()
    assert douglas.answer() == 42

def test_When the argument is 1, the return value is 1.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(1) == '1'
    
def test_When the argument is 2, the return value is 2.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(2) == '2'
    
def test_When the argument is 3, the return value is Fizz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(3) == 'Fizz'
    
def test_When the argument is 6, the return value is Fizz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(6) == 'Fizz'

Do you need so much testing? In this refactoring, we will remove unnecessary tests.

test_fizzbuzz.py


from fizzbuzz import FizzBuzz

    
def test_When the argument is 2, the return value is 2.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(2) == '2'

def test_When the argument is 6, the return value is Fizz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(6) == 'Fizz'

As long as you rub these two things, you can understand that you can process in multiples instead of fixed values.

Removing unnecessary test cases is also a good refactoring.

When the argument is 5, the return value is Buzz

Since I checked the specifications earlier, it is obvious that this is also a multiple of 5, so I will write the test code for cases 5 and 10 and implement it.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 3 == 0):
            return 'Fizz'
        if (arg % 5 == 0):
            return 'Buzz'
        return str(arg)

test_fizzbuzz.py


from fizzbuzz import FizzBuzz

    
def test_When the argument is 2, the return value is 2.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(2) == '2'

def test_When the argument is 6, the return value is Fizz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(6) == 'Fizz'
   
def test_When the argument is 5, the return value is Buzz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(5) == 'Buzz'
    
def test_When the argument is 10, the return value is Buzz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(10) == 'buzz'

スクリーンショット 2019-12-10 18.52.02.png

Oops error, this is the wrong test case. Let's fix it.

Let's delete the unnecessary test code h. Delete 5 cases.

When the argument is 15, the return value is FizzBuzz

Since I checked the specifications earlier, it is obvious that this is also a multiple of 15, so I will write the test code for cases 15 and 30 and implement it.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 3 == 0):
            return 'Fizz'
        if (arg % 5 == 0):
            return 'Buzz'
        if (arg % 15 == 0):
            return 'FizzBuzz'
        return str(arg)

test_fizzbuzz.py


from fizzbuzz import FizzBuzz

    
def test_When the argument is 2, the return value is 2.():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(2) == '2'

def test_When the argument is 6, the return value is Fizz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(6) == 'Fizz'
    
def test_When the argument is 10, the return value is Buzz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(10) == 'Buzz'
    
def test_When the argument is 15, the return value is FizzBuzz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(15) == 'FizzBuzz'
    
def test_When the argument is 30, the return value is FizzBuzz():
    douglas = FizzBuzz()
    assert douglas.fizzbuzz(30) == 'FizzBuzz'
    

スクリーンショット 2019-12-10 19.01.46.png

It seems that it is displayed as Fizz. It seems that multiples of 3 have reacted first. That's also the case, because we check in the order of checking multiples of 3, checking multiples of 5, and checking multiples of 15.

Let's change the order.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        if (arg % 3 == 0):
            return 'Fizz'
        if (arg % 5 == 0):
            return 'Buzz'

        return str(arg)

スクリーンショット 2019-12-10 19.05.21.png

It worked, and you can now deliver it. The scene is a big cheer.

Now for the final refactoring.

It's unpleasant to have if statements lined up, so use else etc. firmly.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        else if (arg % 3 == 0):
            return 'Fizz'
        else if (arg % 5 == 0):
            return 'Buzz'

        return str(arg)

スクリーンショット 2019-12-10 19.10.07.png

Apparently else if is useless, python seems to conditional branch with elif. By the way, let's put the case that outputs numbers in else

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else
            return str(arg)

I will test it. With this, ...

スクリーンショット 2019-12-10 19.12.44.png

I didn't. .. .. ..

You completely forgot the colon.

fizzbuzz.py



class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)

This time it's done. スクリーンショット 2019-12-10 19.13.52.png

It's finally delivered! Applause It will be a drinking party today. I don't have a drinking party.

At the end

The overall flow of TDD when I first tried TDD with MobPro and the details of the failure were just as they were (not so much).

Like this

  1. Write a test
  2. Implement
  3. Refactoring

I'm going through the cycle of TDD, but by doing it with MobPro, I was able to do it with a slightly different feeling and it was very interesting. The result of the test was easy to understand, I tried running it for the time being, and when I got angry, I always googled and then wrote the code, so it was very refreshing.

Mob Pro, let's do it

bonus

** Last and last refactoring **

Someone said. "The language I use has a ternary operator, but what about python?"

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz_bu(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)
            
    def fizzbuzz (self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)

For the time being, I will rewrite it to the ternary operator version while leaving the completed program.

First, there was a directive to replace only the case of multiples of 5 with the ternary operator.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz_bu(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)
            
    def fizzbuzz (self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        arg = 'Buzz' if arg % 5 == 0 else arg

        return str(arg)

スクリーンショット 2019-12-10 19.24.42.png

You can confirm that the test has passed, so the refactoring is successful. Let's replace another case,

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz_bu(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)
            
    def fizzbuzz (self, arg):
        arg = 'FizzBuzz' if arg % 15 == 0 else arg
        arg = 'Fizz' if arg % 3 == 0 else arg
        arg = 'Buzz' if arg % 5 == 0 else arg

        return str(arg)

スクリーンショット 2019-12-10 19.26.54.png

I got a lot of errors. That is also natural. I'm trying to get the remainder of the string.

So what about a single ternary operator? So I tried to erase the contents of else and the substitution.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz_bu(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)
            
    def fizzbuzz (self, arg):
        arg = 'FizzBuzz' if arg % 15 == 0 else 
        'Fizz' if arg % 3 == 0 else 
        'Buzz' if arg % 5 == 0 else 

        return str(arg)

I will test while saying "This will work !!!".

スクリーンショット 2019-12-10 19.32.49.png

Do not do that.

I wonder if line breaks are not good, and when I went through how to divide a long sentence program into multiple lines, there was a voice saying that I could go with a backslash, so I will add it.

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz_bu(self, arg):
        if (arg % 15 == 0):
            return 'FizzBuzz'
        elif (arg % 3 == 0):
            return 'Fizz'
        elif (arg % 5 == 0):
            return 'Buzz'
        else:
            return str(arg)
            
    def fizzbuzz (self, arg):
        arg = 'FizzBuzz' if arg % 15 == 0 else \
        'Fizz' if arg % 3 == 0 else \
        'Buzz' if arg % 5 == 0 else \
        arg
        return str(arg)

スクリーンショット 2019-12-10 19.33.35.png

Hooray! It's a success! !! Cheers will rise as soon as it is completed. Now that we have a ternary operator version, delete the backup and it's time to deliver! !! !! !! !! !! !!

fizzbuzz.py


class FizzBuzz:

    def answer(self):
        return 6 * 7

    def fizzbuzz (self, arg):
        arg = 'FizzBuzz' if arg % 15 == 0 else 
        'Fizz' if arg % 3 == 0 else 
        'Buzz' if arg % 5 == 0 else 

        return str(arg)

Recommended Posts

Getting Started with TDD with Cyber-dojo at MobPro
Getting started with Android!
1.1 Getting Started with Python
Getting Started with Golang 2
Getting Started with Golang 1
Getting Started with Python
Getting Started with Django 1
Getting Started with Optimization
Getting Started with Numpy
Getting Started with Python
Getting Started with Pydantic
Getting Started with Jython
Getting Started with Django 2
Translate Getting Started With TensorFlow
Getting Started with Python Functions
Getting Started with Tkinter 2: Buttons
Getting Started with Go Assembly
Getting Started with PKI with Golang ―― 4
Getting Started with Python Django (1)
Getting Started with Python Django (4)
Getting Started with Python Django (3)
Getting Started with Python Django (6)
Getting Started with Django with PyCharm
Python3 | Getting Started with numpy
Getting Started with Python Django (5)
Getting Started with Python responder v2
Getting Started with Git (1) History Storage
Getting started with Sphinx. Generate docstring with Sphinx
Getting Started with Python Web Applications
Getting Started with Sparse Matrix with scipy.sparse
Getting Started with Julia for Pythonista
Getting Started with Python Basics of Python
Getting Started with Cisco Spark REST-API
Getting started with USD on Windows
Getting started with Python 3.8 on Windows
Getting Started with Python for PHPer-Functions
Getting Started with CPU Steal Time
Getting Started with python3 # 1 Learn Basic Knowledge
Getting Started with Python Web Scraping Practice
Getting Started with Python for PHPer-Super Basics
Getting Started with Python Web Scraping Practice
Getting started with Dynamo from Python boto
Getting started with Python with 100 knocks on language processing
MongoDB Basics: Getting Started with CRUD in JAVA
Getting Started with Drawing with matplotlib: Writing Simple Functions
Getting started with Keras Sequential model Japanese translation
[Translation] Getting Started with Rust for Python Programmers
Django Getting Started Part 2 with eclipse Plugin (PyDev)
Getting started with AWS IoT easily in Python
Getting Started with Python's ast Module (Using NodeVisitor)
Materials to read when getting started with Python
Settings for getting started with MongoDB in python
Grails getting started
Getting Started with python3 # 2 Learn about types and variables
Getting Started with pandas: Basic Knowledge to Remember First
Getting Started with Tensorflow-About Linear Regression Hypothesis and Cost
Materials to read when getting started with Apache Beam
Getting Started with Yocto Project with Raspberry Pi 4 and WSL2
Django 1.11 started with Python3.6
Get started with MicroPython
Get started with Mezzanine