JustToThePoint English Website Version
JustToThePoint en español
JustToThePoint in Thai

Getting started with documenting and testing in Python.

Testing, Debugging, and Deployment

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

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

Documentation and Testing through documentation

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__ = "[email protected]"
__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()

[[email protected] 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 }

Bitcoin donation

JustToThePoint Copyright © 2011 - 2022 PhD. Máximo Núñez Alarcón, Anawim. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.

This website uses cookies to improve your navigation experience.
By continuing, you are consenting to our use of cookies, in accordance with our Cookies Policy and Website Terms and Conditions of use.