Lab 10 - Testing
Due by 11:59pm on 2023-07-18.
Starter Files
Download lab10.zip. Inside the archive, you will find starter files for the questions in this lab.
Topics
Pytest
If you ever want to use your code for something, you need to make sure it works properly. To ensure it works, we use testing. There's several libraries and modules you can use to test your code in an efficient way, and we are going to use pytest.
Installing Pytest
To install pytest, run one of the following
pip install pytest
python3 -m pip install pytest
To make sure you installed it correctly, run one of the following
pytest -h
python3 -m pytest -h
You can uninstall a python library by typing into the terminal
pip uninstall <library name>orpython3 -m pip uninstall <library name>
Using Pytest
Let's say we are trying to test our functions in example.py:
def square(x):
return x * x
def find_factors(n):
factors = []
for i in range(1, n):
if n % i == 0:
factors.append(i)
return factors
We can run our checks using pytest by writing functions that check if the output matches what we expect.
def test_square():
assert square(4) == 16
assert square(0) == 0
assert square(1/2) == 0.25
def test_find_factors():
assert find_factors(15) == [1,3,5,15]
assert find_factors(20) == [1,2,4,5,10,20]
Notice that each of the test functions start with test. In order for pytest to realize that these function are used to verify our code, it must start with test. At this point, we can type one of the following into the terminal
pytest example.py
python3 -m pytest example.py
and it will run all the functions that start with test without us ever needing to call them in our code.
Here's what the output looks like:
We can see that find_factors does not work.
Note: If you're having trouble running pytest, try each of the terminal commands. It's possible only one of the commands work for your operating system
Organization with Test Files
When our there is a lot of code in one file, it's worth moving our tests into a different file for better organization. We will move all our tests into test_example.py and import the functions from example.py:
from example import * # import everything from example.py
def test_square():
assert square(4) == 16
assert square(0) == 0
assert square(1/2) == 0.25
def test_find_factors():
assert find_factors(15) == [1,3,5,15]
assert find_factors(20) == [1,2,4,5,10,20]
Because we have our test file start with test_, running pytest will automatically run this file without us specifying which file we were going to test. For example, instead of typing pytest test_example.py, we can just type:
pytest
It will do the same thing. Pytest will also run all other files in the format test_*.py or *_test.py in the current directory and subdirectories.
Required Questions
Write your code in lab10.py and your tests in test_lab10.py
Q1: Invert and Change
Write a function invert that takes in a non-negative number x and limit. Calculate 1/x and if the result is less than the limit return it, otherwise return limit.
Write a function change that takes in non-negative numbers x, y and limit and returns abs(y - x) / x if it is less than the limit, otherwise return the limit.
Tests for Invert and Change
Write some tests to check if invert and change function correctly. Write two test functions called test_invert and test_change.
When writing the tests, make sure to consider all cases. For example,
invertshould do the following:
- If
1/xis less than thelimitreturn1/x- If
1/xis greater than thelimitreturnlimitWrite tests that check if your code follows these rules by thinking of what inputs would cause each case.
Furthermore, consider if there is a special combination of what
xandlimitcould be that could cause our code to error.
Check your work and run pytest in the terminal:
pytest
Hint: What if
x = 0?
Q2: Refactor
Notice that invert and change have very similar logic in that you are dividing some numerator by x and if the result is greater than the limit then the function returns the limit. Because of this, we can refactor our code so it has the same behavior but with a cleaner design.
To do this we are going to add three new functions:
invert_short- same behavior asinvertbut designed differentlychange_short- same behavior aschangebut designed differentlylimited
limited will have three parameters numerator, denominator and limit. It will contain the logic of dividing a numerator by the denominator and if the result is greater than the limit then the function returns the limit, and it returns the result otherwise. Implement limited.
Now have invert_short and change_short call limited appropriately to maintain the same behavior as invert and change.
Note:
invert_shortandchange_shortshould have only 1 line in its body
Tests for Refactor
Implement two more test functions test_invert_short and test_change_short that ensures that those two functions behave the same as invert and change.
Check your work and run pytest in the terminal:
pytest
Q3: Largest Factor
Aiden is implementing largest_factor which finds the largest factor of a number n that is not n with the exception of 0 and 1:
def largest_factor(n):
biggest_factor = 1
i = 2
while i <= n ** 0.5:
if n % i == 0:
biggest_factor = i
i += 1
return biggest_factor
Recall that you can find if
iis a factor ofnifn % i == 0
Write a function test_largest_factor to check if his code works. If it doesn't work in some cases, figure out where the error is and replace it with something that works.
Check your work and run pytest in the terminal:
pytest
Q4: Catch that Bug! (from CS61a Summer 2021 Exam)
Your classmates are trying to complete a coding assignment but are struggling. The assignment is to write a function digit_counter, which takes in f, a one-argument function, and n, a non-negative integer, and returns the number of digits in n for which f(digit) returns True.
For example, if digit_counter was to find the number of even numbers in 1112, it would return 1.
Laura has decided to use a while loop to complete the assignment, but she's unsure if it works.
def digit_counter(f, n):
counter = 0
while n >= 0:
if f(n % 10):
counter += 1
n = n // 10
return counter
Write some tests in a function called test_digit_counter to check if her code works. If it doesn't work, figure out where the error is and replace it with something that works.
Check your work and run pytest in the terminal:
pytest
Q5: Missing Digits
William is working on a problem called missing digits. The problem states, "Given a number n that is in sorted, non-decreasing order, return the number of missing digits in n. A missing digit is a number between the first and last digit of a that is not in n."
For example, if missing_digits was to find the number of missing numbers in 34, it would return 0 because there are no digits missing between 3 and 4. Other valid inputs include 1, 36, and 1489.
752 would not be a valid input because it is in decreasing order.
def missing_digits(n):
counter = 0
while n > 10:
last_digit = n % 10
second_to_last_digit = (n // 10) % 10
diff = last_digit - second_to_last_digit
counter += diff
n //= 10
return counter
Write a function test_missing_digits to check if his code works. If it doesn't work in some cases, figure out where the error is and replace it with something that works.
Check your work and run pytest in the terminal:
pytest
Hints:
- His approach is that if you have a number like
68you can use8 - 6to find the number of missing digits. How many digits are missing? What about23- What if
n = 1122?
Submit
If you attend the lab, you don't have to submit anything.
If you don't attend the lab, you will have to submit working code. Submit the lab10.py and test_lab10.py files on Canvas to Gradescope in the window on the assignment page.
Grading on Gradescope
If you submit your lab to Gradescope, you will be graded on two things:
- Submitting working functions
- This will require you to write tests to identify the bugs in both the functions you write and the starter functions you're given
- This will be graded with regular tests
- Submitting passing tests
- You should just submit the tests you wrote as you looked for bugs in the functions
- This will be graded by running your tests to make sure they pass
Normally, the starter files come with the tests that the autograder will run. But in this case, doing so would defeat the purpose of having you write tests in the first place! So, unlike other assignments, you won't be given any tests in the starter files.
Note: Gradescope has two naming conventions. As an example, test_invert will
test the actual invert function you submit, and test_test_invert will test
the test_invert test you submit.