Defensive programming, unit tests and documentation

Programming – Session 3

  • Exceptions and assertions
  • Defensive programming
  • Writing tests
  • Documenting and commenting the code

Exceptions and assertions

Exceptions

Runtime errors

  • Syntactic errors are detected by the python interpreter before running the code

  • A code, despite being syntactically correct, may generate errors at runtime:

    • These errors are called exceptions
TAXES_RATE = 1.2
init_price = input("Price without taxes:")
price = init_price * TAXES_RATE
TypeError: can't multiply sequence by non-int of type 'float'

Exceptions and assertions

Exceptions

Recovering from an exception

  • Exceptions are errors occuring at runtime
  • Exceptions can be captured using the try ... except ... statement
# Statements that may raise an exception
try:
    TAXES_RATE = 1.2
    #convert input to int if possible
    init_price = int(input("Price without taxes:"))
    price = init_price * TAXES_RATE

# Capture an exception of type TypeError to handle it
except TypeError as e:
    print(e)

# Continue the execution of the program here

Exceptions and assertions

Exceptions

Raising an exception

  • You can also generate exceptions from your code
  • This can be useful to indicate a particular error
raise NameError('Unexpected behavior')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: Unexpected behavior

Exceptions and assertions

Assertions

What is an assertion

  • A particular case of an exception
  • Assertions document and ensure code execution

How do they differ from exceptions?

  • Only used in development phase
  • Help debuging your code
  • Raise an AssertionError if the test returns False
def euclidean_division (a: int, b: int) -> int:

    # This assertion will fail when a division by 0 is attempted
    assert b != 0, "Division by zero"

    # If we pass all assertions, we are fine
    return a // b

Defensive programming

A programming philosophy

Anticipate the possible errors that may occur at runtime and prevent the program from crashing


Why?

  • Make your code more resilient to unexpected inputs and conditions

  • Ensures that a piece of software will continue to function under unforeseen circumstances

How?

  • Identify problem situations and then corrective actions

  • Validate inputs, avoid assumptions, use safe defaults, and handle errors gracefully

Writing tests

Different types of tests

Tests and documentation together ensure the long-term robustness and sustainability of the software:

  • Unit Tests — Focus on a particular unit (component)

  • Integration Tests — Check the overall compatibility of the different components of an app

  • Regression Tests — check the stability of previously integrated code

  • Security Tests – Avoid code vulnerability

Writing tests

Unit tests

def inverse_capitalization (word: str) -> str:
    result = []
    for char in word:
        result.append(char.lower() if char.isupper() else char.upper())
    return ''.join(result)
# Import the function to test
from my_functions import inverse_capitalization
import unittest

# Define a Unittest class
class TestMyFunctions (unittest.TestCase):

    # Good practice is to define one method per function to test
    def test_inverse_capitalization (self):
        self.assertEqual("hello!", inverse_capitalization("HELLO!"))
    
    # ...

# Start the tests
if __name__ == "__main__":
    unittest.main()

Documenting and commenting the code

To share and reuse code

Documentation

Code without comments and a documentation is useless:

  • Code documentation – Within the code (comments, annotations, and docstrings)
  • Technical documentation – Complement the code with technical details (often diagrams) abour the software architecture
  • User documentation – Focus on the code functionalities from a user point of view

Comments

  • Textual annotations added directly to the source code
  • Explain implementation choices or steps of the algorithm

Docstrings

  • A docstring is a string literal tp describe a module, function, class, or method definition
  • many VSCode extensions exist to automatically generate the docstring from the signature of the function

Documenting and commenting the code

Altogether in an example

def inverse_capitalization (word: str) -> str:

    """
        Inverse the capitalization of a word.
        It consists in iterating over the characters of the word and for each character, inverting its capitalization.
        That is, if a letter is uppercase, it will be transformed to lowercase and vice-versa.
        In:
            * word: A word to inverse the capitalization.
        Out:
            * The word with the capitalization inversed 
    """

    # Temporary list to store the result
    result = []

    # Process character by character to reverse lowercase and uppercase
    for char in word:
        result.append(char.lower() if char.isupper() else char.upper())
    
    # Make a string out of the result list
    return "".join(result)

Recap of the session

Main elements to remember

  • Exceptions are raised when there is a runtime error

  • Assertions are particular types of exceptions for debugging and documenting

  • Defensive programming aims at anticipating possible errors to inform the user

  • Unit tests allow to (semi-automatically) ensure that functions behave as expected

  • Documentation consists in many aspects beyond commenting a code

  • All these elements together allow to write maintainable codes

Recap of the session

What’s next?

Practical activity (~2h30)

  • Defensive programming
  • Tests
  • Documenting your code

After the session

  • Review the articles of the session
  • Check your understanding with the quiz
  • Complete the practical activity
  • Check next session’s “Before the class” section