Homework

5.3.3. Homework#

  • Use these questions after class to check your understanding of the chapter concepts and coding skills.

  • Sign in from the account menu before you start working on the homework.

  • Complete all true/false and coding questions before you submit the homework.

  • Launch Live Code (the rocket) to write and run code in the coding cells.

  • Multiple submissions are allowed. The highest score is kept.

1. Exceptions

A batch import should catch a bad record so the report can explain the problem instead of failing without context.

Hide code cell source

### Solution
# True
# Wrapping the risky step in try/except lets the import catch the exception for a bad
# record and report which record failed and why. Without handling, the first bad record
# stops the whole batch with only a traceback.

2. Specific Exceptions

Catching every exception with a bare except is usually the clearest way to communicate which failure a program expects.

Hide code cell source

### Solution
# False
# A bare except catches every exception, including ones you did not expect, so it hides
# bugs and says nothing about which failure you planned for. Catching a specific
# exception, such as except ValueError, documents the expected failure and lets other
# errors surface.

3. Finally Blocks

A cleanup step that should run whether parsing succeeds or fails belongs naturally in a finally block.

Hide code cell source

### Solution
# True
# Code in a finally block runs whether the try block succeeds, raises an exception that
# except handles, or raises one that is not handled. That makes finally the natural place
# for cleanup steps such as closing a file.

4. Assertions

A small test can use assert to state that a function should return the expected business result.

Hide code cell source

### Solution
# True
# An assert statement checks that a condition is True and raises AssertionError if it is
# not. A test such as assert apply_discount("widget") == 90.0, from Section 5.2, states the
# expected business result directly.

5. Testing Scope

A unit test should rely on the current live weather API being online to test a pure temperature-conversion function.

Hide code cell source

### Solution
# False
# A pure temperature-conversion function depends only on its inputs, so its unit test
# should pass known inputs and check the outputs. Depending on a live API makes the test
# slow and able to fail for reasons unrelated to the function.
### 6. Write pytest-style test functions
# letter_grade(score) below should return "A" for 90 and above, "B" for 80 to 89,
# and "C" below 80. Write three pytest-style test functions that use plain assert
# with a message:
#   test_grade_a: letter_grade(95) == "A", message "95 should be an A"
#   test_grade_b_boundary: letter_grade(80) == "B", message "80 should be a B"
#   test_grade_c: letter_grade(72) == "C", message "72 should be a C"
# Then run the tests the way pytest would: loop over the list
# [test_grade_a, test_grade_b_boundary, test_grade_c], call each test inside
# try/except AssertionError, print f"PASS {test.__name__}" when it passes or
# f"FAIL {test.__name__}: {err}" when it fails, and count the results.
# Do not fix letter_grade; the failing test is the point.
# Print exactly these four lines:
# # PASS test_grade_a
# # FAIL test_grade_b_boundary: 80 should be a B
# # PASS test_grade_c
# # 2 passed, 1 failed
def letter_grade(score):
    if score >= 90:
        return "A"
    elif score > 80:
        return "B"
    else:
        return "C"

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
def letter_grade(score):
    if score >= 90:
        return "A"
    elif score > 80:
        return "B"
    else:
        return "C"

def test_grade_a():
    assert letter_grade(95) == "A", "95 should be an A"

def test_grade_b_boundary():
    assert letter_grade(80) == "B", "80 should be a B"

def test_grade_c():
    assert letter_grade(72) == "C", "72 should be a C"

passed = 0
failed = 0
for test in [test_grade_a, test_grade_b_boundary, test_grade_c]:
    try:
        test()
        print(f"PASS {test.__name__}")
        passed += 1
    except AssertionError as err:
        print(f"FAIL {test.__name__}: {err}")
        failed += 1

print(f"{passed} passed, {failed} failed")
PASS test_grade_a
FAIL test_grade_b_boundary: 80 should be a B
PASS test_grade_c
2 passed, 1 failed
### 7. Guard division
# Define `percent(part, whole)` that raises `ValueError` when whole is zero. Print exactly these two lines:
# # 25.0
# # ValueError: whole cannot be zero
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
def percent(part, whole):
    if whole == 0:
        raise ValueError("whole cannot be zero")
    return round(part / whole * 100, 1)

print(percent(5, 20))
try:
    print(percent(3, 0))
except ValueError as err:
    print("ValueError:", err)
25.0
ValueError: whole cannot be zero
### 8. Run a parametrized test table
# @pytest.mark.parametrize runs one test over many (input, expected) rows.
# Do the same by hand: loop over cases, call shipping_fee(weight) for each row,
# and compare the actual fee with the expected fee.
# For each failing row, print f"FAIL shipping_fee({weight}): expected {expected}, got {actual}".
# After the loop, print how many cases passed. Do not fix shipping_fee.
# Print exactly these two lines:
# # FAIL shipping_fee(1): expected 5, got 8
# # 4 of 5 cases passed
def shipping_fee(weight):
    if weight < 1:
        return 5
    elif weight <= 5:
        return 8
    return 12

cases = [(0.5, 5), (1, 5), (3, 8), (5, 8), (7.5, 12)]

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
def shipping_fee(weight):
    if weight < 1:
        return 5
    elif weight <= 5:
        return 8
    return 12

cases = [(0.5, 5), (1, 5), (3, 8), (5, 8), (7.5, 12)]

passed = 0
for weight, expected in cases:
    actual = shipping_fee(weight)
    if actual == expected:
        passed += 1
    else:
        print(f"FAIL shipping_fee({weight}): expected {expected}, got {actual}")

print(f"{passed} of {len(cases)} cases passed")
FAIL shipping_fee(1): expected 5, got 8
4 of 5 cases passed
### 9. Test with a fake dependency
# average_rating(product_id, fetch) gets its ratings by calling fetch(product_id).
# The real fetch_reviews would call an external API, so a unit test must not use it.
# Instead of patching, pass in a fake:
#   1. Create an empty list named calls.
#   2. Define fake_fetch(product_id) that appends product_id to calls and returns
#      canned data: [5, 4, 4, 3, 5] for "p1" and [] for "p2".
#   3. Write test_average_typical (average_rating("p1", fake_fetch) == 4.2) and
#      test_average_no_reviews (average_rating("p2", fake_fetch) == 0.0).
#   4. Loop over [test_average_typical, test_average_no_reviews], call each test,
#      and print f"PASS {test.__name__}". Then print the calls the fake recorded.
# Print exactly these three lines:
# # PASS test_average_typical
# # PASS test_average_no_reviews
# # fake_fetch called with: ['p1', 'p2']
def fetch_reviews(product_id):
    raise RuntimeError("This would call the reviews API")

def average_rating(product_id, fetch):
    ratings = fetch(product_id)
    if len(ratings) == 0:
        return 0.0
    return round(sum(ratings) / len(ratings), 1)

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
def fetch_reviews(product_id):
    raise RuntimeError("This would call the reviews API")

def average_rating(product_id, fetch):
    ratings = fetch(product_id)
    if len(ratings) == 0:
        return 0.0
    return round(sum(ratings) / len(ratings), 1)

calls = []

def fake_fetch(product_id):
    calls.append(product_id)
    canned = {"p1": [5, 4, 4, 3, 5], "p2": []}
    return canned[product_id]

def test_average_typical():
    assert average_rating("p1", fake_fetch) == 4.2

def test_average_no_reviews():
    assert average_rating("p2", fake_fetch) == 0.0

for test in [test_average_typical, test_average_no_reviews]:
    test()
    print(f"PASS {test.__name__}")

print("fake_fetch called with:", calls)
PASS test_average_typical
PASS test_average_no_reviews
fake_fetch called with: ['p1', 'p2']
### 10. Validate a function input
# Define `clean_label(label)` that:
#   - raises `TypeError("label must be a string")` if `label` is not a string
#   - otherwise returns `label.strip().title()` (removes the outer spaces and capitalizes each word)
# Then print `clean_label(" q1 revenue ")`, and call `clean_label(42)` inside
# `try`/`except TypeError` and print the error. Print exactly these two lines:
# # Q1 Revenue
# # TypeError: label must be a string
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
def clean_label(label):
    if not isinstance(label, str):
        raise TypeError("label must be a string")
    return label.strip().title()

print(clean_label(" q1 revenue "))
try:
    print(clean_label(42))
except TypeError as err:
    print("TypeError:", err)
Q1 Revenue
TypeError: label must be a string