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> or python3 -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:

pytest_example

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, invert should do the following:

  • If 1/x is less than the limit return 1/x
  • If 1/x is greater than the limit return limit

Write 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 x and limit could 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 as invert but designed differently
  • change_short - same behavior as change but designed differently
  • limited

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_short and change_short should 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 i is a factor of n if n % 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 68 you can use 8 - 6 to find the number of missing digits. How many digits are missing? What about 23
  • 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.


© 2023 Brigham Young University, All Rights Reserved