Exercises for Lesson 18

Back to Lesson 18

Exercise 1: A simple unit test

We can use the unittest Python module to test our code. For example, consider the following function:

def doStuff(x, y):
    if x < y:
        return 7
    else:
        return x-y

Part a: happy path

The “happy path” for this function is testing the usual code paths when things go well. What should the return values be for doStuff(4, 10) and doStuff(20, 10)?

Here is a simple Python program that will test these happy path cases, with two lines for you to fill in:

import unittest

def doStuff(x, y):
    if x < y:
        return 7
    else:
        return x-y
    
class Test_Happy(unittest.TestCase):

    def testIf(self):
        expected = # TODO: what number do we expect in this case?
        actual = doStuff(4, 10)
        self.assertEqual(expected, actual)

    def testElse(self):
        expected = # TODO: what number do we expect in this case?
        actual = doStuff(20, 10)
        self.assertEqual(expected, actual)

if __name__ == "__main__":
    unittest.main(verbosity=2)

Fill in the values to make the tests pass.

Part b: edge cases

We should also check for edge cases (also known as boundary conditions). For this program, that would be when we’re on the border of the if condition. For example, what if x and y are equal? Add a test case for this:

class Test_Edge(unittest.TestCase):

    def testInputsEqual(self):
        # TODO: add code to test for this case
        pass # replace with your code

Part c: invalid cases

Realistically, we shouldn’t trust our users. We should also check for really unexpected inputs. For this function, that may be values that aren’t numbers. Maybe we really just want ints and floats.

Write some test cases with inputs that aren’t numbers.

Part d: test-driven development

The tests you wrote for Part c should fail. Add some error handling to doStuff to handle this. Hint: the function isinstance(obj, types) takes in a variable and a type (or tuple of types). For example:

x = 4
y = 2.3
z = [x, y]
isinstance(x, int) # True
isinstance(x, float) # False
isinstance(x, (int, float)) # True
isinstance(y, (int, float)) # True
isinstance(z, (int, float)) # False

With your changes to doStuff, your tests from Part c should now pass.

Back to Lesson 18

Exercise 2: Map

Your goal is to write a class named Map, which allows us to associate keys with values. For example, we may want to map the string "blue" to the number 3 and the string "yellow" to the number 4.

The Map class should have the following methods:

  • Map() constructor: takes no actual parameters, but initializes necessary data structures; returns None
  • add(key, value): adds a new mapping from key to value (or updates the mapping if key is already present); returns None
  • get(key): looks up the value mapped to by key; returns the value for key or None if no mapping exists for key
  • getSize(): calculates the number of key-value pairs; returns that number (an int)

Think carefully about what you need to store to be able to handle any number of key-value pairs.

You should create a new file named map.py and put your class definition there. It should start like this:

class Map:
    # TODO: write the class!

To get you started, here is some code you can use to test your implementation. Copy this and save it as mapTest.py in the same folder as map.py.

## Unit tests for Map class

import unittest

from map import *

#########################################################
## constructor (by itself)                             ##
#########################################################

# For now, just make sure the class exists
class Test_1_constructor(unittest.TestCase):

    def testExists(self):
        map = Map()
        self.assertIsNotNone(map)

#########################################################
## add method (by itself)                              ##
#########################################################

# For now, just make sure add exists
class Test_2_add(unittest.TestCase):

    def testAddFromEmpty(self):
        map = Map()

        actual = map.add("blue", 314)
        self.assertIsNone(actual)

    def testAddUpdate(self):
        map = Map()

        actual = map.add("blue", 314)
        self.assertIsNone(actual)

        actual = map.add("blue", 271)
        self.assertIsNone(actual)

#########################################################
## get method (with constructor, add)                  ##
#########################################################

class Test_3_get(unittest.TestCase):

    def testGetEmpty(self):
        map = Map()

        actual = map.get("blue")
        self.assertIsNone(actual)

    def testGetNotFound(self):
        map = Map()
        map.add("blue", 314)
        
        actual = map.get("yellow")
        self.assertIsNone(actual)

    def testGetSinglePair(self):
        map = Map()
        key = "blue"
        value = 314
        map.add(key, value)

        expected = value
        actual = map.get(key)
        self.assertEqual(expected, actual)

    def testGetMultiplePairs(self):
        map = Map()
        key = "blue"
        value = 314
        map.add(key, value)
        map.add("yellow", 271)

        expected = value
        actual = map.get(key)
        self.assertEqual(expected, actual)

    def testGetUpdated(self):
        map = Map()
        key = "blue"
        oldValue = 314
        newValue = 271
        map.add(key, oldValue)
        map.add(key, newValue)

        expected = newValue
        actual = map.get(key)
        self.assertEqual(expected, actual)

#########################################################
## get method (with constructor, add)                  ##
#########################################################

class Test_4_getSize(unittest.TestCase):

    def testGetSizeEmpty(self):
        map = Map()
        
        expected = 0
        actual = map.getSize()
        self.assertEqual(expected, actual)

    def testGetSizeSinglePair(self):
        map = Map()
        map.add("blue", 314)

        expected = 1
        actual = map.getSize()
        self.assertEqual(expected, actual)

    def testGetSizeMultiplePairs(self):
        map = Map()
        map.add("blue", 314)
        map.add("yellow", 271)

        expected = 2
        actual = map.getSize()
        self.assertEqual(expected, actual)

    def testGetSizeUpdated(self):
        map = Map()
        map.add("blue", 314)
        map.add("blue", 271)

        expected = 1
        actual = map.getSize()
        self.assertEqual(expected, actual)

#########################################################
## run tests                                           ##
#########################################################

if __name__ == "__main__":
    unittest.main(verbosity=2)

Back to Lesson 18