Python: testing and loops
Python code
We’ll start with the Python code we ended with last time, in a file called
registration.py
:
def add_shipping(subtotal: int) -> int: """add shipping amount to subtotal""" if subtotal <= 50: return subtotal + 10 elif subtotal > 50 and subtotal <= 100: return subtotal + 20 else: return subtotal + 30 def register(reg_list: list, course: str): """add the course to the reg_list""" reg_list.append(course)
Testing with pytest
Unlike Pyret (with where
-blocks), Python does not have a built-in way to write
tests. For this class, we’re using a Python library called pytest
for
testing. You’ll write your tests in a separate file. Since our file is called
registration.py
. we’ll call our test file test_registration.py
. We can write
a test for our add_shipping
function:
from registration import * def test_add_shipping(): assert add_shipping(40) == 50 assert add_shipping(50) == 60 assert add_shipping(51) == 71 assert add_shipping(100) == 120 assert add_shipping(101) == 131
The first line imports our definitions from the registration.py
file. Our test
is called test_add_shipping
; test names have to start with test_
. The test
is a function with a number of assert
statements; think of assert f(x) == y
in a Python test as being just like f(x) is y
in a Pyret where
-block.
A better registration function
What happens if we do this in the Python console?
> taking = ["CSCI0111"] > register(taking, "CSCI0111")
It seems like we shouldn’t be able to register for the same course twice! So,
let’s write a new register
function:
def register(reg_list: list, course: str): if course not in reg_list: reg_list.append(course)
Notice: this if
doesn’t have an else
! Why is this allowed in Python but not
Pyret?
Right now, register
just fails silently if we try to re-register for a
course. We should probably throw an error when this happens:
def register(reg_list: list, course: str): if course in reg_list: raise Exception("Already registered") else: reg_list.append(course)
Let’s test this function. Over in test_registration
, we can do:
# at top import pytest def test_register(): courses = [] register(courses, "CSCI 0111") assert courses == ["CSCI 0111"] pytest.raises(Exception, register, courses, "CSCI 0111")
Let’s get loopy
Let’s say we want to sum the numbers in a list. We know how to do this in Pyret:
fun sum(lst :: List<Number>) -> Number: cases (List) lst: | empty => 0 | link(fst, rst) => fst + sum(rst) end end
In Python, we’ll do things a little differently. We’ll use the fact that we can change the value of a variable and write a loop over every element of the list. It looks like this:
def sum(lst: list) -> int: running_sum = 0 for n in lst: running_sum = running_sum + n return running_sum
We can find the components of the Pyret cases
version here. At the beginning,
running_sum
is zero; that’s just like the empty
case. Inside the loop we’re
adding n
to the running_sum
, just like we’re adding fst
to sum(rst)
. So:
what’s this for-loop doing? It’s taking each element of the list, naming it
n
, and letting us do something with it–in this case, adding it to
running_sum
.
So, how would we write a function that takes a list of classes and returns a list of all the classes in a particular department?
def courses_in_dept(courses: list, dept: str) -> list: found_courses = [] for course in courses: if course.startswith(dept): found_courses.append(course) return found_courses