Table of Contents

Computer Lab 13, unit tests, exceptions, list comprehensions

Class inheritance

BASE CLASS

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname
 
    def printname(self):
        print(self.firstname, self.lastname)
 
if __name__ == "__main__":
 
    p1 = Person("Jane", "Doe")
    # p2 = Person("Jane", "Doe", 2019) won't work
    p1.printname()
    # p1.welcome() won't work      

CHILD CLASS

class Student(Person):
    def __init__(self, fname, lname, year):
        super().__init__(fname, lname)
        self.graduationyear = year
 
    def welcome(self):
        print("Welcome", self.firstname, self.lastname, "to the class of", self.graduationyear)
 
if __name__ == "__main__":
 
    s1 = Student("Mike", "Olsen", 2019)
    s1.printname()
    s1.welcome()

Unit tests

  1. Create your inputs
  2. Execute the code being tested, capturing the output
  3. Compare the output with an expected result. Validation of the output against a known response is known as an assertion.
  1. You put your tests into classes as methods
  2. You use a series of special assertion methods in the unittest.TestCase class instead of the built-in assert statement.
  3. Here are some of the most commonly used methods:
Method Equivalent to
.assertEqual(a, b) a == b
.assertNotEqual(a, b) a != b
.assertAlmostEqual(a, b) round(a-b, 7) == 0
.assertGreater(a, b) a > b
.assertTrue(x) bool(x) is True
.assertFalse(x) bool(x) is False
.assertIs(a, b) a is b
.assertIsNone(x) x is None
.assertIn(a, b) a in b
.assertIsInstance(a, b) isinstance(a, b)
.assertRaises(SomeException)

Testing modules/libraries

File structure:

/prg_12
...__init__.py (empty file)
...mymath.py
.../tests
......__init__.py (empty file)
......test_mymath.py

mymath.py (module to test)

def abs(x):
    if isinstance(x, int) or isinstance(x, float):
        if x >= 0:
            return x
        else:
            return -x
    else:
        raise ValueError
 
def aprox_cos(x):
    if isinstance(x, int) or isinstance(x, float):
        return 1 - x ** 2 / 2 + x ** 4 / 24
    else:
        raise ValueError
 
def aprox_sin(x):
    if isinstance(x, int) or isinstance(x, float):
        return x - x ** 3 / 6 + x ** 5 / 120
    else:
        raise ValueError

test_mymath.py (unit tests)

import unittest
 
from prg_13_test.mymath import abs
 
 
class TestMyMath(unittest.TestCase):
    def test_abs_positive(self):
        """
        Test that abs() of a positive number is exactly the same number
        """
        data = 8
        result = abs(data)
        self.assertEqual(result, 8)
 
    def test_abs_negative(self):
        """
        Test that abs() of a negative number is an additive inverse of the same number
        """
        data = -8
        result = abs(data)
        self.assertEqual(result, 8)
 
    def test_abs_string(self):
        """
        Test that abs() raises a ValueError when it is called with a string.
        """
        data = "666"
        with self.assertRaises(ValueError):
            result = abs(data)
 

Testing classes

point.py (class definition)

import math
 
class Point:
 
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
    def __str__(self):
        return "x = {}, y = {}".format(self.x, self.y)
 
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
 
    def get_norm(self):
        norm = math.sqrt(self.x ** 2 + self.y ** 2)
        return norm
 
    def is_unit(self):
        return self.get_norm() == 1
 
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

test_point.py (unit test)

import unittest
 
from prg_13_test.point import Point
 
class TestPoint(unittest.TestCase):
    def setUp(self):
        self.p = Point(2, 5)
 
    def test_init(self):
        '''
        Test that p.x and p.y are set correctly.
        '''
        self.assertEqual(self.p.x, 2)
        self.assertEqual(self.p.y, 5)
 
    def test_str(self):
        '''
        Test that the __str__() returns a string in a correct format.
        '''
        string_p = str(self.p)
        self.assertEqual(string_p, "x = 2, y = 5")
 
    def test_eq(self):
        '''
        Test that the __eq__() function works as expected.
        '''
        self.assertEqual(self.p, Point(2, 5))
        self.assertNotEqual(self.p, Point(0, 5))
        self.assertNotEqual(self.p, Point(2, 0))
        self.assertNotEqual(self.p, Point(0, 0))
 
    def test_add(self):
        '''
        Test that the + operation return a correct type and answer
        '''
        p2 = Point(-5, 8)
        sum_p = self.p + p2
        self.assertIsInstance(sum_p, Point)
        self.assertEqual(sum_p, Point(-3, 13))
 
    def test_is_unit(self):
        '''
        Test that the test_is_unit() works as expected.
        '''
        p1 = Point(1, 0)
        p2 = Point(0, 1)
        p3 = Point(1, 1)
        p4 = Point(3/5, 4/5)
 
        self.assertTrue(p1.is_unit())
        self.assertTrue(p2.is_unit())
        self.assertFalse(p3.is_unit())
        self.assertTrue(p4.is_unit)

Custom exceptions

In previous weeks, you were asked to implement the following piecewise function: \[ f(x)= \begin{cases} (x-6)^2+5 & 6 \le x \\ 5 & 4 \leq x\lt 6 \\ \frac{5}{2}x-5 & 2 \leq x\lt 4 \\ \text{undefined} & x \lt 2 \end{cases} \]

Init() and str() implemented.

class UndefinedFunctionValueError(Exception):
 
    def __init__(self, message):
        self.message = message
 
    def __str__(self):
        return 'UndefinedFunctionValueError: {}'.format(self.message)
 
 
def piecewise_function(x):
    if x >= 6:
        return (x - 6) ** 2 + 5
    elif x >= 4:
        return 5
    elif x >= 2:
        return 5 / 2 * x - 5
    else:
        raise UndefinedFunctionValueError("The Function is not defined for: {}.".format(x))
 
 
 
if __name__ == "__main__":
    x1 = piecewise_function(8)
    print("f(8) = ", x1)
    x2 = piecewise_function(0)
    print("f(0) = ", x2)

Exception takes a number instead of a string.

class UndefinedFunctionValueError(Exception):
 
    def __init__(self, x_value):
        self.x_value = x_value
 
    def __str__(self):
        return 'UndefinedFunctionValueError: The Function is not defined for: {}'.format(self.x_value)
 
 
def piecewise_function(x):
    if x >= 6:
        return (x - 6) ** 2 + 5
    elif x >= 4:
        return 5
    elif x >= 2:
        return 5 / 2 * x - 5
    else:
        raise UndefinedFunctionValueError(x)
 
 
 
if __name__ == "__main__":
    x1 = piecewise_function(8)
    print("f(8) = ", x1)
    x2 = piecewise_function(0)
    print("f(0) = ", x2)

A class uses its parent class.

class UndefinedFunctionValueError(Exception):
 
    def __init__(self, x_value):
        message = "The Function is not defined for: {}.".format(x_value)
        super().__init__(message)
 
 
def piecewise_function(x):
    if x >= 6:
        return (x - 6) ** 2 + 5
    elif x >= 4:
        return 5
    elif x >= 2:
        return 5 / 2 * x - 5
    else:
        raise UndefinedFunctionValueError(x)
 
 
 
if __name__ == "__main__":
    x1 = piecewise_function(8)
    print("f(8) = ", x1)
    x2 = piecewise_function(0)
    print("f(0) = ", x2)

Inheritance from the ValueError class.

class UndefinedFunctionValueError(ValueError):
 
    def __init__(self, message):
        self.message = message
 
    def __str__(self):
        return 'UndefinedFunctionValueError: {}'.format(self.message)
 
 
def piecewise_function(x):
    if x >= 6:
        return (x - 6) ** 2 + 5
    elif x >= 4:
        return 5
    elif x >= 2:
        return 5 / 2 * x - 5
    else:
        raise UndefinedFunctionValueError("The Function is not defined for: {}.".format(x))
 
 
 
if __name__ == "__main__":
    try:
        x1 = piecewise_function(8)
        print("f(8) = ", x1)
    except UndefinedFunctionValueError as e:
        print(e)
 
    try:    
        x2 = piecewise_function(0)
        print("f(0) = ", x2)
    except UndefinedFunctionValueError as e:
        print(e)

List Comprehension

newlist = [expression for item in iterable if condition == True]

fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
 
newlist = [x for x in fruits if x != "apple"]
sentence = 'the rocket came back from mars'
>>> vowels = [i for i in sentence if i in 'aeiou']
>>> vowels
['e', 'o', 'e', 'a', 'e', 'a', 'o', 'a']

newlist = [x for x in fruits]

# list
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
 
newlist1 = [x for x in fruits]
newlist2 = [x for x in fruits if 'a' in x]
newlist3 = [x for x in fruits if x != "apple"]
 
# string
anti_letters = [letter for letter in 'anti­dis­establishment­arian­ism']
 
# range
range_list1 = [x for x in range(10)]
range_list2 = [x for x in range(10) if x < 5]

The expression is the current item in the iteration, but it is also the outcome, which you can manipulate before it ends up like a list item in the new list:

squares = [i * i for i in range(10)]
 
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
newlist = [x.upper() for x in fruits]

Weekly homework 13

  1. Implement a custom exception with a class InvalidPasswordFormatError (inherits from ValueError).
  2. Its __str__() method should return a string in the following format: Reason: <string passed to the constructor of the object>.
  3. Implement a fuction (in the global scope) is_valid_password(pswd) which works as suggested by following listings:

if __name__ == "__main__":
    try:
        print(is_valid_password("abcdefghij"))
    except InvalidPasswordFormatError as e:
        print(e)

True

if __name__ == "__main__":
    try:
        print(is_valid_password("abcde"))
    except InvalidPasswordFormatError as e:
        print(e)

Reason: Your password is too short.

if __name__ == "__main__":
    try:
        print(is_valid_password("abcdefefefe8"))
    except InvalidPasswordFormatError as e:
        print(e)

Reason: Your password must contain only letters.

if __name__ == "__main__":
    try:
        print(is_valid_password("abcd8e"))
    except InvalidPasswordFormatError as e:
        print(e)

Reason: Your password is too short. Your password must contain only letters.

  • Password is too short if it is shorter than 8 characters.
  • Place both the class definition (the custom exception) and the function into a single module 13_weekly_hw.py.