Exercises for 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 int
s and float
s.
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.
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; returnsNone
add(key, value)
: adds a new mapping fromkey
tovalue
(or updates the mapping ifkey
is already present); returnsNone
get(key)
: looks up the value mapped to bykey
; returns the value forkey
orNone
if no mapping exists forkey
getSize()
: calculates the number of key-value pairs; returns that number (anint
)
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)