Write test-driven FizzBuzz code using Python doctest.

Introduction

I wrote a test-driven FizzBuzz program using the standard library doctest.

What is doctest (quote)

http://docs.python.jp/2/library/doctest.html (Begin quote) The doctest module looks for text that looks like an interactive Python session, executes the contents of the session, and sees if it behaves exactly as it is written. doctest is often used for the following purposes.

Check that the contents of the docstring are up-to-date by verifying that all of the interactive execution examples in the module's docstring (documentation string) work as written. Regression testing is achieved by verifying that the interactive execution examples in the test file or test object work as expected. You can write a tutorial document for a package that uses a lot of input / output examples. A document can be a "readable test" or a "executable document", depending on whether you focus on I / O examples or commentary. (End of citation)

doctest seems to be an excellent tool that can be used for regression testing while checking documents (comments). I have no choice but to try this.

Test-driven development method (quote)

http://www.atmarkit.co.jp/ait/articles/1403/05/news035.html (Begin citation (headline only)) [1] Write test code that fails [2] Write the code to be tested so that the test changes from failure to success [3] Add test code that causes the test to fail [4] Write the code to be tested so that the test changes from failure to success [5] Refactor the test with success (End of citation)

This time, we will follow this method.

Specifications of this function

I decided the specifications appropriately. Function name: fizzbuzz (n) function: If the input value n is a multiple of 3, the string'Fizz' is returned and If the input value n is a multiple of 5, the string'Buzz'is returned and If the input value n is a multiple of 15, it returns the string'FizzBuzz' and Other than that, a function that outputs an integer n.

n must be an integer represented by an integer type, a long integer type, or a floating point number type. (For floating point minority type, the fractional part must be 0.) If a small number is entered, an InputError exception will be returned.

[1] Write test code that fails

First of all, temporary implementation

def fizzbuzz(n):
  '''
If the input value n is a multiple of 3'Fizz'Returns the string
If the input value n is a multiple of 5'Buzz'Returns the string
If the input value n is a multiple of 15'FizzBuzz'Returns the string
Other than that, a function that outputs an integer n.
Example:
  >>> fizzbuzz(3)
  'Fizz'
  
n must be an integer represented by an integer type, a long integer type, or a floating point number type.
(For floating point minority type, the fractional part must be 0.)
If a small number is entered, InputError will be returned.
  '''
  
  return 'Fizz'

if __name__ == '__main__':
  import doctest
  doctest.testmod()

Run (-v is an option for detailed output). If you do not add -v, the information will be output only when the test fails.

python fizzbuzz_doctest.py -v 

In this state, ok comes out.

Try adding a test case

>>> fizzbuzz(5)
'Buzz'

The following error is output.

File "fizzbuzz_doctest.py", line12, in __main__.fizzbuzz
Failed example:
    fizzbuzz(5)
Expected:
    'Buzz'
Got:
    'Fizz'
1 items had no tests:
    __main__

[2] Write the code to be tested so that the test changes from failure to success

Modify as follows.

  if (n%3) == 0:
    return 'Fizz'
  elif (n%5) == 0:
    return 'Buzz'

[3] Add test code that causes the test to fail

Repeating the above [1]

[4] Write the code to be tested so that the test changes from failure to success

Repeating the above [2]

Supplement

Exception testing seems to require starting with Traceback. Also, you can omit the middle line with ....

  >>> fizzbuzz(5.5)
  Traceback (most recent call last):
      ...
  ValueError: n must be integer. n: 5.500000

Code (intermediate stage)

# coding: utf-8

def fizzbuzz(n):
  '''
If the input value n is a multiple of 3'Fizz'Returns the string
If the input value n is a multiple of 5'Buzz'Returns the string
If the input value n is a multiple of 15'FizzBuzz'Returns the string
Other than that, a function that outputs an integer n.
Example:
  >>> fizzbuzz(3)
  'Fizz'
  
  >>> fizzbuzz(36)
  'Fizz'
  
  >>> fizzbuzz(5)
  'Buzz'
  
  >>> fizzbuzz(50)
  'Buzz'
  
  >>> fizzbuzz(15)
  'FizzBuzz'
  
  >>> fizzbuzz(45)
  'FizzBuzz'
  
  >>> fizzbuzz(2)
  2
  
  >>> fizzbuzz(49)
  49
  
n must be an integer represented by an integer type, a long integer type, or a floating point number type.
(For floating point minority type, the fractional part must be 0.)
If a small number is entered, ValueError will be returned.
  
  >>> fizzbuzz(1.0)
  1

  >>> fizzbuzz(5.0)
  'Buzz'
  
  >>> fizzbuzz(5.5)
  Traceback (most recent call last):
      ...
  ValueError: n must be integer. n: 5.500000
  
  '''
  if n != int(n):
    raise ValueError( 'n must be integer. n: %f' % n )
  n = int(n)

  if (n%15) == 0:
    return 'FizzBuzz'
  elif (n%3) == 0:
    return 'Fizz'
  elif (n%5) == 0:
    return 'Buzz'
  else:
    return n

if __name__ == '__main__':
  import doctest
  doctest.testmod()

[5] Refactor the test with success

It seems that the final code can be obtained by refactoring while ensuring the operation by testing.

For example, you can make the following changes: In order to reduce the reference cost of the if statement, create a list whose elements are the remainder divided by 15 of each of 1 to 15 in advance, and determine the character string to be returned by referring to this list.

  '''
  if (n%15) == 0:
    return 'FizzBuzz'
  elif (n%3) == 0:
    return 'Fizz'
  elif (n%5) == 0:
    return 'Buzz'
  else:
    return n
  '''
  
  fizzbuzz_list = []
  for i in range(15):
    if (i%15) == 0:
      fizzbuzz_list.append('FizzBuzz')
    elif (i%5) == 0:
      fizzbuzz_list.append('Buzz')
    elif (i%3) == 0:
      fizzbuzz_list.append('Fizz')
    else:
      fizzbuzz_list.append(0)
  r = int(n%15)
  return fizzbuzz_list[r] if fizzbuzz_list[r] else n

You can perform a regression test with the following command without writing a new test.

python fizzbuzz_doctest.py -v

Let's change the algorithm further.

  '''
  if (n%15) == 0:
    return 'FizzBuzz'
  elif (n%3) == 0:
    return 'Fizz'
  elif (n%5) == 0:
    return 'Buzz'
  else:
    return n

  
  fizzbuzz_list = []
  for i in range(15):
    if (i%15) == 0:
      fizzbuzz_list.append('FizzBuzz')
    elif (i%5) == 0:
      fizzbuzz_list.append('Buzz')
    elif (i%3) == 0:
      fizzbuzz_list.append('Fizz')
    else:
      fizzbuzz_list.append(0)
  '''

  fizzbuzz_list =  ['FizzBuzz', 0, 0, 'Fizz', 0, 'Buzz', 'Fizz', 0, 0, 'Fizz', 'Buzz', 0, 'Fizz', 0, 0 ]
  r = n%15
  return fizzbuzz_list[r] if fizzbuzz_list[r] else n

You can run the regression test with the following command as before.

python fizzbuzz_doctest.py -v

Final code

Not only the code but also the documentation is complete.

# coding: utf-8

def fizzbuzz(n):
  '''
If the input value n is a multiple of 3'Fizz'Returns the string
If the input value n is a multiple of 5'Buzz'Returns the string
If the input value n is a multiple of 15'FizzBuzz'Returns the string
Other than that, a function that outputs an integer n.
Example:
  >>> fizzbuzz(3)
  'Fizz'
  
  >>> fizzbuzz(36)
  'Fizz'
  
  >>> fizzbuzz(5)
  'Buzz'
  
  >>> fizzbuzz(50)
  'Buzz'
  
  >>> fizzbuzz(15)
  'FizzBuzz'
  
  >>> fizzbuzz(45)
  'FizzBuzz'
  
  >>> fizzbuzz(2)
  2
  
  >>> fizzbuzz(49)
  49
  
n must be an integer represented by an integer type, a long integer type, or a floating point number type.
(For floating point minority type, the fractional part must be 0.)
If a small number is entered, ValueError will be returned.
  
  >>> fizzbuzz(1.0)
  1
  
  >>> fizzbuzz(5.0)
  'Buzz'
  
  >>> fizzbuzz(5.5)
  Traceback (most recent call last):
      ...
  ValueError: n must be integer. n: 5.500000
  
  '''
  if n != int(n):
    raise ValueError( 'n must be integer. n: %f' % n )
  n = int(n)

  fizzbuzz_list = ['FizzBuzz', 0, 0, 'Fizz', 0, 'Buzz', 'Fizz', 0, 0, 'Fizz', 'Buzz', 0, 'Fizz', 0, 0 ]
  r = n%15
  return fizzbuzz_list[r] if fizzbuzz_list[r] else n

if __name__ == '__main__':
  import doctest
  doctest.testmod()

Conclusion

It seems to be easy to use because you can test and create documents at once. It has been pointed out that the code becomes difficult to read because the comment becomes long, but the two advantages of leaving the document are better maintainability and the quality can be guaranteed by executing the regression test are greater. feel like.

Recommended Posts

Write test-driven FizzBuzz code using Python doctest.
Write FizzBuzz without using "="
Write selenium test code in python
Check python code styles using pep8
[VS Code] ~ Tips when using python ~
Write Ethereum contract code using Serpent
Write Python code that applies type information at runtime using pydantic
Execute Python code on C ++ (using Boost.Python)
Getting Python source code metrics using radon
Write FizzBuzz using map (), reduce (), filter (), recursion
Write python modules in fortran using f2py
Notes on using code formatter in Python
python setup.py test the code using multiprocess
FizzBuzz with Python3
Write python-like code
Let's write FizzBuzz with an error: Python Version
Debug with VS Code using boost python numpy
Start using Python
python character code
FizzBuzz in Python
[Python] Algorithm-aware code
Scraping using Python
I want to write in Python! (1) Code format check
[Unity (C #), Python] Try running Python code in Unity using IronPython
Directory structure for test-driven development using pytest in python
[Python] I immediately tried using Pylance's VS Code extension.
Let's write python code that parses go code and generates go code
How to make a Python package using VS Code
Write data to KINTONE using the Python requests module
Read and write NFC tags in python using PaSoRi
Write code to Unit Test a Python web app
[Introduction to Python] How to write repetitive statements using for statements
Write Python-like code (dictionary)
Operate Redmine using Python Redmine
Fibonacci sequence using Python
Python code acceleration approach
Data analysis using Python 0
Rewrite Python2 code to Python3 (2to3)
infomap python draw code
Before writing Python code
Write Python in MySQL
Data cleaning using Python
About Python3 character code
Using Python #external packages
WiringPi-SPI communication using Python
Age calculation using python
Search Twitter using Python
Name identification using python
Python Requests status code
Notes using Python subprocesses
OpenCV basic code (python)
I tried using doctest
Try using Tweepy [Python2.7]
[Introduction to Python] How to write conditional branches using if statements
[Python] Chapter 01-03 About Python (Write and execute a program using PyCharm)