If it compiles, ship it
Testing your code is extremely important before deploying your code to production. It is not optional.
Software testing is an essential part of the software development life cycle. Its aim is to ensure that the code to be deployed has no bugs, crashes, or logical errors. You need to make sure that it meets the standards of your company, as well as being truly useful, readable, and efficient.
unittest is the test module in the Python standard library so it comes with Python out of the box. In other words, it is the testing framework set in Python by default. In unittest, we create classes that are derived from the unittest.TestCase class.
We are going to test our fraction class.
import unittest # We import the unnitest module from the standard library.
from fraction import Fraction
class TestFraction(unittest.TestCase): # We need to create a class TestFraction that inherits from the unittest.TestCase class.
def setUp(self): # It is executed before our tests are run. We are going to define three fractions that will be used for all our tests.
print("setUp")
self.fraction1 = Fraction(2,-3)
self.fraction2 = Fraction(3,-4)
self.fraction3 = Fraction(4, -6)
def test_add(self): # All our tests are methods from the TestFraction class.
print("test_add")
self.resultado = self.fraction1 + self.fraction2
self.assertEqual(self.resultado, Fraction(-17, 12)) # assertEqual() is a function that checks if its two first arguments are equal.
def test_sub(self):
print("test_sub")
self.resultado = self.fraction1 - self.fraction2
self.assertEqual(self.resultado, Fraction(1, 12))
def test_mul(self):
print("test_mul")
self.resultado = self.fraction1 * self.fraction2
self.assertEqual(self.resultado, Fraction(1, 2))
def test_div(self):
print("test_div")
self.resultado = self.fraction1 / self.fraction2
self.assertEqual(self.resultado, Fraction(8, 9))
def test_init(self):
print("test_init")
with self.assertRaises(ZeroDivisionError): # assertRaises() allows an exception to be handled by our test. In other words, the code can throw an exception without exiting the execution, as is normally the case for unhandled exceptions.
Fraction(1, 0)
with self.assertRaises(TypeError):
Fraction(1.2, 3)
Fraction(a, 2)
def test_equal(self):
print("test_equal")
self.assertEqual(self.fraction1 == self.fraction3, True)
def test_float(self):
print("test_float")
self.assertEqual(round(float(self.fraction3), 2), -0.67)
if __name__ == '__main__':
unittest.main()
python testFraction.py
setUp test_add .
setUp test_div .
setUp test_equal .
setUp test_float .
setUp test_init .
setUp test_mul .
setUp test_sub .
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
Ran 7 tests in 0.000s OK
Code tells you how; Comments tell you why, Jeff Atwood
Software quality measures how well the software is designed (quality of design) and conforms to that design (quality of conformance). Documentation and testing are an essential part of developing software that yields high-quality software and accurate schedule predictability.
Single-line comments start with a hash character (#), and should be followed by a short, sweet, and to-the-point explanation.
# It calculates the factorial of a number using recursion.
>>> def factorial(n):
... return 1 if (n==1 or n==0) else n * factorial(n - 1)
It is quite common and good practice to start a Python file with a few lines of comments. The basic structure could be something like:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
# It provides support for fractions in Python [...]
#‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
__author__ = "Maximo Nunez Alarcon" # Authorship information
__copyright__ = "Copyright 2021, JustToThePoint"
__license__ = "GPL"
__version__ = "1.0.1"
__maintainer__ = "Maximo Nunez Alarcon"
__email__ = "nmaximo7@gmail.com"
__status__ = "Production"
import doctest
Python docstrings are the default way of documenting Python code. They are enclosed by three double quotes """ and they should be found after the definition of a function, method, class, or module.
Doctests sit within a function after the def statement and before the code of the function. The test case becomes a formal part of the class or API documentation. The doctest module searches your code for pieces of text in docstrings that look like interactive Python sessions and executes them to confirm that they work exactly as shown and expected.
class Fraction:
"""A simple implementation of fractions in Python"""
[...]
def __add__(self, other):
""" Adds a fraction to this fraction.
:params other the fraction to add.
:return the new Fraction object that is the result of this + other.
>>> str(Fraction(2,-3) + Fraction(3,-4))
'-1 5/12'
"""
if type(other) == type(int):
other = Fraction(other)
num = self.numerator * other.denominator + self.denominator * other.numerator
den = self.denominator * other.denominator
return Fraction(num, den)
[...]
if __name__ == '__main__':
doctest.testmod()
python fraction.py -v # We pass -v to the script so doctest prints a detailed log of what it’s trying and prints a summary.
Trying: str(Fraction(2,-3) + Fraction(3,-4)) Expecting: ‘-1 5/12’ ok
Let’s see another example. We are going to implement a priority queue.
import doctest
import heapq
import random
from heapq import heappush, heappop
from copy import deepcopy
class PriorityQueue:
""" It implements a priority queue.
A priority queue sorts and dequeues elements based on their priorities.
Args:
heap : It uses the heap data structure provided by heapq to create a priority queue.
The smallest element will be popped out the first from the queue regardless of the order in which the elements were queued.
"""
def __init__(self):
self.heap = []
def push(self, e):
"""
It inserts a new element 'e'.
>>> myQueue = PriorityQueue.createFromList([12, 9, 3, 14, 29, 4, 5, 13, 18])
>>> myQueue.push(7)
>>> print(myQueue)
{ 3 4 5 7 9 12 13 14 18 29 }
"""
heappush(self.heap, e) # heappush insert the element e into the heap, _but the order is adjusted to maintain the heap properties_.
def pop(self):
"""
It removes the minimum element from the queue.
>>> PriorityQueue.createFromList([12, 9, 3, 14, 9, 4, 5, 13, 18]).pop()
3
"""
return heappop(self.heap) # heappop removes the smallest element from the heap, _but the order is adjusted to maintain the heap properties_.
def is_empty(self):
"""
It returns true when the queue is empty; otherwise, the function returns false.
>>> PriorityQueue.createFromList([12, 9, 3, 14, 9, 4, 5, 13, 18]).is_empty()
False
>>> PriorityQueue.createFromList([]).is_empty()
True
"""
return len(self.heap)==0
def __len__(self):
"""
It returns the number of elements in the priority queue.
>>> len(PriorityQueue.createFromList([12, 9, 3, 14, 29, 4, 5, 13, 18]))
9
"""
return len(self.heap)
def __str__(self):
"""
It allows us to print out our priority queue.
>>> print(PriorityQueue.createFromList([12, 9, 3, 14, 29, 4, 5, 13, 18]))
{ 3 4 5 9 12 13 14 18 29 }
"""
copy = deepcopy(self)
s = "{"
while not copy.is_empty():
s += " " + str(copy.pop())
s += " }"
return s
@classmethod
def createFromList(cls, myList):
"""
It takes as input a list and transforms it into a priority queue.
"""
myPriorityQueue = PriorityQueue()
heapq.heapify(myList) # heapify converts the list into a heap data structure.
myPriorityQueue.heap = myList
return myPriorityQueue
@classmethod
def createRandomPriorityQueue(cls, n):
"""
It creates a priority queue with n random numbers.
"""
myList = [random.randint(1, 20) for i in range(n)]
myPriorityQueue = PriorityQueue()
for i in range(n):
i = random.randint(0, 100)
myPriorityQueue.push(i)
return myPriorityQueue
def main():
myQueue = PriorityQueue.createFromList([12, 9, 3, 14, 29, 4, 5, 13, 18])
print(myQueue)
myQueue.push(7)
print(myQueue)
print(len(myQueue))
print(myQueue.pop())
print(myQueue)
my2Queue = PriorityQueue.createRandomPriorityQueue(7)
print(my2Queue)
if __name__ == '__main__':
doctest.testmod()
main()
[nmaximo7@myarch myPython]$ python priorityQueue.py -v
Trying: len(PriorityQueue.createFromList([12, 9, 3, 14, 29, 4, 5, 13, 18])) Expecting: 9 ok
Trying: print(PriorityQueue.createFromList([12, 9, 3, 14, 29, 4, 5, 13, 18])) Expecting: { 3 4 5 9 12 13 14 18 29 } ok
[…] 8 tests in 11 items. 8 passed and 0 failed. Test passed. { 3 4 5 9 12 13 14 18 29 } { 3 4 5 7 9 12 13 14 18 29 } 10 3 { 4 5 7 9 12 13 14 18 29 } { 47 49 56 78 79 91 97 }